diff --git a/.changeset/configuration-hub-organization-specs.md b/.changeset/configuration-hub-organization-specs.md new file mode 100644 index 000000000..251a2bd42 --- /dev/null +++ b/.changeset/configuration-hub-organization-specs.md @@ -0,0 +1,11 @@ +--- +"@epilot/configuration-hub-client": minor +"@epilot/organization-client": minor +"@epilot/sdk": minor +"@epilot/cli": minor +--- + +Refresh the Configuration Hub and Organization API contracts from the published specs. + +- Configuration Hub: preview orgs (`createPreview`, `listPreviews`, `getPreview`) and sync job health (`getSyncJobHealth`, `runSyncJobHealth`). +- Organization: announcements (`getActiveAnnouncement`, `acknowledgeAnnouncement`), `notifySettingsValueUpdated`, and the `Preview` organization use. Internal-only schemas (e.g. `InternalOrganization`, `CreateOrganizationRequest`, `OrganizationToCleanup`) are no longer part of the public client; use `@epilot/organization-client-internal`. diff --git a/clients/configuration-hub-client/src/openapi-runtime.json b/clients/configuration-hub-client/src/openapi-runtime.json index 1efdedb6c..99b1c6dcb 100644 --- a/clients/configuration-hub-client/src/openapi-runtime.json +++ b/clients/configuration-hub-client/src/openapi-runtime.json @@ -240,6 +240,28 @@ "responses": {} } }, + "/v1/configs/sync-jobs/{id}/health": { + "get": { + "operationId": "getSyncJobHealth", + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } + ], + "responses": {} + } + }, + "/v1/configs/sync-jobs/{id}/health:run": { + "post": { + "operationId": "runSyncJobHealth", + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } + ], + "responses": {} + } + }, "/v1/configs/sync-jobs/{id}/retry": { "post": { "operationId": "retrySyncJob", @@ -502,6 +524,35 @@ ], "responses": {} } + }, + "/v1/previews": { + "post": { + "operationId": "createPreview", + "requestBody": { + "required": false, + "content": { + "application/json": {} + } + }, + "responses": {} + }, + "get": { + "operationId": "listPreviews", + "responses": {} + } + }, + "/v1/previews/{id}": { + "get": { + "operationId": "getPreview", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true + } + ], + "responses": {} + } } }, "components": { diff --git a/clients/configuration-hub-client/src/openapi.d.ts b/clients/configuration-hub-client/src/openapi.d.ts index e03ac2181..37247be7d 100644 --- a/clients/configuration-hub-client/src/openapi.d.ts +++ b/clients/configuration-hub-client/src/openapi.d.ts @@ -306,6 +306,33 @@ declare namespace Components { */ target_id: string; } + export interface CreatePreviewRequest { + /** + * Display name of the preview. Defaults to `Preview `. + */ + name?: string; + /** + * Internal callers only (e.g. the admin portal, with the source org in `x-ivy-org-id`): + * who gets invited to the preview org as owner; `user_id` or `email` is required. Users + * always invite themselves; sending this with a user token is rejected with 403. + * + */ + requester?: { + /** + * A user of the source org (preferred). They are invited, and the copy job acts as + * them (e.g. they own the copied dashboards). Email, name and language come from + * their user record. + * + */ + user_id?: string; + /** + * Used when `user_id` is not given. + */ + email?: string; // email + display_name?: string; + language?: "de" | "en"; + }; + } /** * Bulk-delete job header surfaced by `createDeleteJob`/`getDeleteJob`. */ @@ -418,6 +445,10 @@ declare namespace Components { export type DeleteJobStatus = "pending" | "in_progress" | "succeeded" | "partial" | "failed" | "cancelled"; export interface ErrorResponse { status: number; + /** + * Machine-readable reason, where the operation documents one + */ + code?: string; error: string; } export interface HealthFinding { @@ -425,7 +456,7 @@ declare namespace Components { * Stable finding identity — unchanged across rescans. */ id: string; - 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"; + 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"; severity: "error" | "warning" | "info"; /** * Config type carrying the problem. @@ -534,6 +565,11 @@ declare namespace Components { status: "scanning" | "failed"; error?: string; } + export interface HealthSeverityCounts { + error: number; + warning: number; + info: number; + } /** * Result of an index rebuild operation */ @@ -587,6 +623,54 @@ declare namespace Components { title?: string; } | null; } + export interface Preview { + id: string; + name: string; + status: /** + * - creating_org: the preview org is being created + * - copying_configuration: the sync job copying this org's configuration is running + * - inviting: the requester is being invited to the preview org + * - ready: the requester got the "your preview org is ready" email + * - failed: see `error` + * + */ + PreviewStatus; + /** + * The org the preview copies (the org it was requested from). + */ + source_org_id: string; + /** + * The preview org, once created. + */ + preview_org_id?: string; + /** + * The sync job copying the configuration (see `getSyncJob`). + */ + sync_job_id?: string; + /** + * Final status of the copy sync job (`succeeded` or `partial`). + */ + sync_status?: string; + requested_by: { + user_id?: string; + email?: string; + }; + created_at: string; // date-time + updated_at: string; // date-time + /** + * Why the preview failed. + */ + error?: string; + } + /** + * - creating_org: the preview org is being created + * - copying_configuration: the sync job copying this org's configuration is running + * - inviting: the requester is being invited to the preview org + * - ready: the requester got the "your preview org is ready" email + * - failed: see `error` + * + */ + export type PreviewStatus = "creating_org" | "copying_configuration" | "inviting" | "ready" | "failed"; /** * Configuration resource type identifier. * Matches blueprint-manifest-api V3 naming conventions. @@ -723,6 +807,13 @@ declare namespace Components { current_batch?: /* Position within the current topological batch for the active phase. */ SyncJobBatch; started_at: string; // date-time finished_at?: string; // date-time + health_check?: /** + * Compact state of the job's post-import health check. Full results: + * `getSyncJobHealth`. Absent on plans and on jobs that finished before + * the check existed. + * + */ + SyncJobHealthCheck; /** * Deprecated — use `listSyncJobEvents`. Up to 10 op-log events, * ordered by resource index rather than recency. @@ -1054,6 +1145,67 @@ declare namespace Components { */ source: "events" | "op_log"; } + /** + * Targeted health check of the configs a sync job wrote into the target + * org. Findings carry `first_seen` = `last_seen` = `finished_at`. + * + */ + export interface SyncJobHealth { + status: /** + * State of a sync job's post-import health check. `pending`: queued, + * `running`: scanning, `done`: findings available, `failed`: the check + * itself failed or did not finish (the import is unaffected), `skipped`: + * nothing to check (a plan, a job that wrote nothing, or a cancelled + * job), `not_run`: a finished job that never had a check (start one with + * `runSyncJobHealth`). + * + */ + SyncJobHealthStatus; + started_at?: string; // date-time + finished_at?: string; // date-time + /** + * Written configs whose payload was read. + */ + scanned: number; + counts: HealthSeverityCounts; + /** + * Why the check failed or was skipped. + */ + error?: string; + /** + * Errors first, then warnings, then info. Empty unless `done`. + */ + findings: HealthFinding[]; + } + /** + * Compact state of the job's post-import health check. Full results: + * `getSyncJobHealth`. Absent on plans and on jobs that finished before + * the check existed. + * + */ + export interface SyncJobHealthCheck { + status: /** + * State of a sync job's post-import health check. `pending`: queued, + * `running`: scanning, `done`: findings available, `failed`: the check + * itself failed or did not finish (the import is unaffected), `skipped`: + * nothing to check (a plan, a job that wrote nothing, or a cancelled + * job), `not_run`: a finished job that never had a check (start one with + * `runSyncJobHealth`). + * + */ + SyncJobHealthStatus; + counts: HealthSeverityCounts; + } + /** + * State of a sync job's post-import health check. `pending`: queued, + * `running`: scanning, `done`: findings available, `failed`: the check + * itself failed or did not finish (the import is unaffected), `skipped`: + * nothing to check (a plan, a job that wrote nothing, or a cancelled + * job), `not_run`: a finished job that never had a check (start one with + * `runSyncJobHealth`). + * + */ + export type SyncJobHealthStatus = "pending" | "running" | "done" | "failed" | "skipped" | "not_run"; /** * Cursor-paginated list of sync jobs. */ @@ -1754,6 +1906,14 @@ declare namespace Paths { export type $400 = Components.Schemas.ErrorResponse; } } + namespace CreatePreview { + export type RequestBody = Components.Schemas.CreatePreviewRequest; + namespace Responses { + export type $202 = Components.Schemas.Preview; + export interface $409 { + } + } + } namespace CreateSyncJob { export type RequestBody = /** * Request body for `createSyncJob`. `target_auth_token` is the destination @@ -1847,6 +2007,19 @@ declare namespace Paths { export type $200 = /* Current index build state */ Components.Schemas.IndexStatusResponse; } } + namespace GetPreview { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $200 = Components.Schemas.Preview; + export interface $404 { + } + } + } namespace GetSyncJob { namespace Parameters { export type Id = string; @@ -1864,6 +2037,23 @@ declare namespace Paths { export type $404 = Components.Schemas.ErrorResponse; } } + namespace GetSyncJobHealth { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $200 = /** + * Targeted health check of the configs a sync job wrote into the target + * org. Findings carry `first_seen` = `last_seen` = `finished_at`. + * + */ + Components.Schemas.SyncJobHealth; + export type $404 = Components.Schemas.ErrorResponse; + } + } namespace GetSyncPlan { namespace Parameters { export type Id = string; @@ -1974,6 +2164,13 @@ declare namespace Paths { export type $200 = /* Cursor-paginated list of delete jobs. */ Components.Schemas.DeleteJobListResponse; } } + namespace ListPreviews { + namespace Responses { + export interface $200 { + results: Components.Schemas.Preview[]; + } + } + } namespace ListSyncJobEvents { namespace Parameters { export type After = string; @@ -2182,6 +2379,24 @@ declare namespace Paths { export type $404 = Components.Schemas.ErrorResponse; } } + namespace RunSyncJobHealth { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $202 = /** + * Targeted health check of the configs a sync job wrote into the target + * org. Findings carry `first_seen` = `last_seen` = `finished_at`. + * + */ + Components.Schemas.SyncJobHealth; + export type $404 = Components.Schemas.ErrorResponse; + export type $409 = Components.Schemas.ErrorResponse; + } + } namespace SetHealthFindingsIgnored { export interface RequestBody { finding_ids: string[]; @@ -2493,6 +2708,50 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * getSyncJobHealth - getSyncJobHealth + * + * Targeted health check of an applied (non-dry-run) sync job. When the job + * reaches its terminal status, the target org is scanned asynchronously + * for the configs the job wrote — rows with status `created` or + * `patched` and a `target_id` — using the same finding logic as + * `getHealthReport`, plus `source_org_reference` for written payloads + * that still carry a source-org id, and `secret_value_missing` (warning) + * for a referenced SecretString variable the job created: a sync copies + * a variable's key, never its value, so setting it is a manual step. It runs for `succeeded` and + * `partial` jobs, and for `failed` jobs that wrote at least one config; + * otherwise it is `skipped`. Best-effort: it never changes the job's own + * status, and its results never replace the org-wide health report. + * Readable from both the source and the target org. + * + * A finished job that never had a check (it finished before checks + * existed) reports `not_run`. A check left `pending` or `running` for + * longer than the scan can take is reported as `failed`; both can be + * started again with `runSyncJobHealth`. + * + */ + 'getSyncJobHealth'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * runSyncJobHealth - runSyncJobHealth + * + * Start the targeted health check of a finished, applied sync job again: + * for jobs that finished before checks existed (`not_run`), checks that + * failed or got stuck, or to re-check after fixing something. Same scope + * and rules as the automatic check after every import. Callable from the + * source or the target org; the scan runs in the target org. Answers with + * the check's new state (`pending`) and runs asynchronously; poll + * `getSyncJobHealth`. + * + */ + 'runSyncJobHealth'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse /** * retrySyncJob - retrySyncJob * @@ -2765,6 +3024,42 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * listPreviews - listPreviews + * + * Lists the previews requested from the caller's current org, newest first (max 50). + */ + 'listPreviews'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createPreview - createPreview + * + * Starts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`, + * plus `requester`): a new, temporary org with + * `organization_use: Preview` (parent = this org) that receives a copy of all of + * this org's configuration through a sync job. When the copy is done, the + * requesting user is invited to the preview org as owner with a + * "your preview org is ready" email. Runs asynchronously; poll `getPreview`. + * + */ + 'createPreview'( + parameters?: Parameters | null, + data?: Paths.CreatePreview.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getPreview - getPreview + * + * Returns a preview requested from the caller's current org, with its progress. + */ + 'getPreview'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse } export interface PathsDictionary { @@ -3062,6 +3357,54 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v1/configs/sync-jobs/{id}/health']: { + /** + * getSyncJobHealth - getSyncJobHealth + * + * Targeted health check of an applied (non-dry-run) sync job. When the job + * reaches its terminal status, the target org is scanned asynchronously + * for the configs the job wrote — rows with status `created` or + * `patched` and a `target_id` — using the same finding logic as + * `getHealthReport`, plus `source_org_reference` for written payloads + * that still carry a source-org id, and `secret_value_missing` (warning) + * for a referenced SecretString variable the job created: a sync copies + * a variable's key, never its value, so setting it is a manual step. It runs for `succeeded` and + * `partial` jobs, and for `failed` jobs that wrote at least one config; + * otherwise it is `skipped`. Best-effort: it never changes the job's own + * status, and its results never replace the org-wide health report. + * Readable from both the source and the target org. + * + * A finished job that never had a check (it finished before checks + * existed) reports `not_run`. A check left `pending` or `running` for + * longer than the scan can take is reported as `failed`; both can be + * started again with `runSyncJobHealth`. + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/configs/sync-jobs/{id}/health:run']: { + /** + * runSyncJobHealth - runSyncJobHealth + * + * Start the targeted health check of a finished, applied sync job again: + * for jobs that finished before checks existed (`not_run`), checks that + * failed or got stuck, or to re-check after fixing something. Same scope + * and rules as the automatic check after every import. Callable from the + * source or the target org; the scan runs in the target org. Answers with + * the check's new state (`pending`) and runs asynchronously; poll + * `getSyncJobHealth`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } ['/v1/configs/sync-jobs/{id}/retry']: { /** * retrySyncJob - retrySyncJob @@ -3364,6 +3707,46 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v1/previews']: { + /** + * createPreview - createPreview + * + * Starts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`, + * plus `requester`): a new, temporary org with + * `organization_use: Preview` (parent = this org) that receives a copy of all of + * this org's configuration through a sync job. When the copy is done, the + * requesting user is invited to the preview org as owner with a + * "your preview org is ready" email. Runs asynchronously; poll `getPreview`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.CreatePreview.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * listPreviews - listPreviews + * + * Lists the previews requested from the caller's current org, newest first (max 50). + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/previews/{id}']: { + /** + * getPreview - getPreview + * + * Returns a preview requested from the caller's current org, with its progress. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } } export type Client = OpenAPIClient @@ -3381,6 +3764,7 @@ export type ConfigListResponse = Components.Schemas.ConfigListResponse; export type ConfigNode = Components.Schemas.ConfigNode; export type ConfigTypeInfo = Components.Schemas.ConfigTypeInfo; export type ConfirmLineageRequest = Components.Schemas.ConfirmLineageRequest; +export type CreatePreviewRequest = Components.Schemas.CreatePreviewRequest; export type DeleteJob = Components.Schemas.DeleteJob; export type DeleteJobCounts = Components.Schemas.DeleteJobCounts; export type DeleteJobListResponse = Components.Schemas.DeleteJobListResponse; @@ -3393,10 +3777,13 @@ export type ErrorResponse = Components.Schemas.ErrorResponse; export type HealthFinding = Components.Schemas.HealthFinding; export type HealthReportResponse = Components.Schemas.HealthReportResponse; export type HealthScanStartResponse = Components.Schemas.HealthScanStartResponse; +export type HealthSeverityCounts = Components.Schemas.HealthSeverityCounts; export type IndexRebuildResponse = Components.Schemas.IndexRebuildResponse; export type IndexStatusResponse = Components.Schemas.IndexStatusResponse; export type LineageEntryResponse = Components.Schemas.LineageEntryResponse; export type MatchSuggestion = Components.Schemas.MatchSuggestion; +export type Preview = Components.Schemas.Preview; +export type PreviewStatus = Components.Schemas.PreviewStatus; export type ResourceType = Components.Schemas.ResourceType; export type SnapshotInventoryItem = Components.Schemas.SnapshotInventoryItem; export type SnapshotInventorySkippedType = Components.Schemas.SnapshotInventorySkippedType; @@ -3410,6 +3797,9 @@ export type SyncJobCounts = Components.Schemas.SyncJobCounts; export type SyncJobEvent = Components.Schemas.SyncJobEvent; export type SyncJobEventDependency = Components.Schemas.SyncJobEventDependency; export type SyncJobEventListResponse = Components.Schemas.SyncJobEventListResponse; +export type SyncJobHealth = Components.Schemas.SyncJobHealth; +export type SyncJobHealthCheck = Components.Schemas.SyncJobHealthCheck; +export type SyncJobHealthStatus = Components.Schemas.SyncJobHealthStatus; export type SyncJobListResponse = Components.Schemas.SyncJobListResponse; export type SyncJobRequest = Components.Schemas.SyncJobRequest; export type SyncJobResource = Components.Schemas.SyncJobResource; diff --git a/clients/configuration-hub-client/src/openapi.json b/clients/configuration-hub-client/src/openapi.json index f855b5abe..198dfd256 100644 --- a/clients/configuration-hub-client/src/openapi.json +++ b/clients/configuration-hub-client/src/openapi.json @@ -39,6 +39,10 @@ { "name": "Health", "description": "Configuration health scans, reports, coverage requests, cleanup and ignored findings" + }, + { + "name": "Previews", + "description": "Preview orgs — temporary copies of an org's configuration" } ], "paths": { @@ -972,6 +976,102 @@ } } }, + "/v1/configs/sync-jobs/{id}/health": { + "get": { + "operationId": "getSyncJobHealth", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getSyncJobHealth", + "description": "Targeted 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` or\n`patched` and a `target_id` — using the same finding logic as\n`getHealthReport`, plus `source_org_reference` for written payloads\nthat still carry a source-org id, and `secret_value_missing` (warning)\nfor a referenced SecretString variable the job created: a sync copies\na variable's key, never its value, so setting it is a manual step. It runs for `succeeded` and\n`partial` jobs, and for `failed` jobs that wrote at least one config;\notherwise it is `skipped`. Best-effort: it never changes the job's own\nstatus, and its results never replace the org-wide health report.\nReadable from both the source and the target org.\n\nA finished job that never had a check (it finished before checks\nexisted) reports `not_run`. A check left `pending` or `running` for\nlonger than the scan can take is reported as `failed`; both can be\nstarted again with `runSyncJobHealth`.\n", + "tags": [ + "Sync", + "Health" + ], + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } + ], + "responses": { + "200": { + "description": "Health check of the job's written configs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncJobHealth" + } + } + } + }, + "404": { + "description": "Sync job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/v1/configs/sync-jobs/{id}/health:run": { + "post": { + "operationId": "runSyncJobHealth", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "runSyncJobHealth", + "description": "Start 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. Same scope\nand rules as the automatic check after every import. Callable from the\nsource or the target org; the scan runs in the target org. Answers with\nthe check's new state (`pending`) and runs asynchronously; poll\n`getSyncJobHealth`.\n", + "tags": [ + "Sync", + "Health" + ], + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } + ], + "responses": { + "202": { + "description": "Health check started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncJobHealth" + } + } + } + }, + "404": { + "description": "Sync job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "`job_not_applied` (a plan: nothing was written), `job_not_finished`\n(the job is still running), or `health_check_running` (a check is\nalready pending or running)\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, "/v1/configs/sync-jobs/{id}/retry": { "post": { "operationId": "retrySyncJob", @@ -1903,6 +2003,122 @@ } } } + }, + "/v1/previews": { + "post": { + "operationId": "createPreview", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "createPreview", + "description": "Starts 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 copy of all of\nthis org's configuration through a sync job. When the copy is done, the\nrequesting user is invited to the preview org as owner with a\n\"your preview org is ready\" email. Runs asynchronously; poll `getPreview`.\n", + "tags": [ + "Previews" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatePreviewRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Preview creation started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Preview" + } + } + } + }, + "409": { + "description": "Too many previews are being created for this org at the same time" + } + } + }, + "get": { + "operationId": "listPreviews", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "listPreviews", + "description": "Lists the previews requested from the caller's current org, newest first (max 50).", + "tags": [ + "Previews" + ], + "responses": { + "200": { + "description": "Previews of this org", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Preview" + } + } + }, + "required": [ + "results" + ] + } + } + } + } + } + } + }, + "/v1/previews/{id}": { + "get": { + "operationId": "getPreview", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getPreview", + "description": "Returns a preview requested from the caller's current org, with its progress.", + "tags": [ + "Previews" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The preview", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Preview" + } + } + } + }, + "404": { + "description": "Preview not found in this org" + } + } + } } }, "components": { @@ -2283,7 +2499,10 @@ "orphaned_price", "duplicate_price", "inactive_price_in_use", - "incomplete_configuration" + "incomplete_configuration", + "source_org_reference", + "secret_value_missing", + "env_value_missing" ] }, "severity": { @@ -2793,6 +3012,10 @@ "status": { "type": "integer" }, + "code": { + "type": "string", + "description": "Machine-readable reason, where the operation documents one" + }, "error": { "type": "string" } @@ -3229,6 +3452,94 @@ "level" ] }, + "SyncJobHealthStatus": { + "type": "string", + "description": "State 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 finished job that never had a check (start one with\n`runSyncJobHealth`).\n", + "enum": [ + "pending", + "running", + "done", + "failed", + "skipped", + "not_run" + ] + }, + "HealthSeverityCounts": { + "type": "object", + "required": [ + "error", + "warning", + "info" + ], + "properties": { + "error": { + "type": "integer" + }, + "warning": { + "type": "integer" + }, + "info": { + "type": "integer" + } + } + }, + "SyncJobHealthCheck": { + "type": "object", + "description": "Compact 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", + "required": [ + "status", + "counts" + ], + "properties": { + "status": { + "$ref": "#/components/schemas/SyncJobHealthStatus" + }, + "counts": { + "$ref": "#/components/schemas/HealthSeverityCounts" + } + } + }, + "SyncJobHealth": { + "type": "object", + "description": "Targeted health check of the configs a sync job wrote into the target\norg. Findings carry `first_seen` = `last_seen` = `finished_at`.\n", + "required": [ + "status", + "scanned", + "counts", + "findings" + ], + "properties": { + "status": { + "$ref": "#/components/schemas/SyncJobHealthStatus" + }, + "started_at": { + "type": "string", + "format": "date-time" + }, + "finished_at": { + "type": "string", + "format": "date-time" + }, + "scanned": { + "type": "integer", + "description": "Written configs whose payload was read." + }, + "counts": { + "$ref": "#/components/schemas/HealthSeverityCounts" + }, + "error": { + "type": "string", + "description": "Why the check failed or was skipped." + }, + "findings": { + "type": "array", + "description": "Errors first, then warnings, then info. Empty unless `done`.", + "items": { + "$ref": "#/components/schemas/HealthFinding" + } + } + } + }, "SyncJob": { "type": "object", "description": "Sync job header as surfaced by `getSyncJob` and the create response. The\ncanonical persistence shape is described in `docs/sync/INTERFACES.md`.\n", @@ -3271,6 +3582,9 @@ "type": "string", "format": "date-time" }, + "health_check": { + "$ref": "#/components/schemas/SyncJobHealthCheck" + }, "events": { "type": "array", "description": "Deprecated — use `listSyncJobEvents`. Up to 10 op-log events,\nordered by resource index rather than recency.\n", @@ -4226,6 +4540,114 @@ "resources", "skipped_types" ] + }, + "CreatePreviewRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100, + "description": "Display name of the preview. Defaults to `Preview `." + }, + "requester": { + "type": "object", + "description": "Internal callers only (e.g. the admin portal, with the source org in `x-ivy-org-id`):\nwho gets invited to the preview org as owner; `user_id` or `email` is required. Users\nalways invite themselves; sending this with a user token is rejected with 403.\n", + "properties": { + "user_id": { + "type": "string", + "description": "A user of the source org (preferred). They are invited, and the copy job acts as\nthem (e.g. they own the copied dashboards). Email, name and language come from\ntheir user record.\n" + }, + "email": { + "type": "string", + "format": "email", + "description": "Used when `user_id` is not given." + }, + "display_name": { + "type": "string" + }, + "language": { + "type": "string", + "enum": [ + "de", + "en" + ] + } + } + } + } + }, + "PreviewStatus": { + "type": "string", + "enum": [ + "creating_org", + "copying_configuration", + "inviting", + "ready", + "failed" + ], + "description": "- 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" + }, + "Preview": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/PreviewStatus" + }, + "source_org_id": { + "type": "string", + "description": "The org the preview copies (the org it was requested from)." + }, + "preview_org_id": { + "type": "string", + "description": "The preview org, once created." + }, + "sync_job_id": { + "type": "string", + "description": "The sync job copying the configuration (see `getSyncJob`)." + }, + "sync_status": { + "type": "string", + "description": "Final status of the copy sync job (`succeeded` or `partial`)." + }, + "requested_by": { + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string", + "description": "Why the preview failed." + } + }, + "required": [ + "id", + "name", + "status", + "source_org_id", + "requested_by", + "created_at", + "updated_at" + ] } } } diff --git a/clients/organization-client/src/openapi-runtime.json b/clients/organization-client/src/openapi-runtime.json index b66955623..db68b3c4d 100644 --- a/clients/organization-client/src/openapi-runtime.json +++ b/clients/organization-client/src/openapi-runtime.json @@ -94,6 +94,30 @@ "responses": {} } }, + "/v2/organization/{org_id}/settings/{key}/notifications": { + "post": { + "operationId": "notifySettingsValueUpdated", + "parameters": [ + { + "in": "path", + "name": "org_id", + "required": true + }, + { + "in": "path", + "name": "key", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, "/v2/organization/feature-settings": { "get": { "operationId": "getOrganizationFeatureSettings", @@ -106,9 +130,40 @@ "deprecated": true, "responses": {} } + }, + "/v2/organization/announcements/active": { + "get": { + "operationId": "getActiveAnnouncement", + "responses": {} + } + }, + "/v2/organization/announcements/{announcement_id}/acknowledge": { + "post": { + "operationId": "acknowledgeAnnouncement", + "parameters": [ + { + "$ref": "#/components/parameters/AnnouncementIdPathParam" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": {} + } + }, + "responses": {} + } + } + }, + "components": { + "parameters": { + "AnnouncementIdPathParam": { + "in": "path", + "name": "announcement_id", + "required": true + } } }, - "components": {}, "servers": [ { "url": "https://organization-v2.sls.epilot.io" diff --git a/clients/organization-client/src/openapi.d.ts b/clients/organization-client/src/openapi.d.ts index c34bb84cf..b28200051 100644 --- a/clients/organization-client/src/openapi.d.ts +++ b/clients/organization-client/src/openapi.d.ts @@ -7,103 +7,117 @@ import type { } from 'openapi-client-axios'; declare namespace Components { + namespace Parameters { + export type AnnouncementIdPathParam = string; + } + export interface PathParameters { + AnnouncementIdPathParam?: Parameters.AnnouncementIdPathParam; + } namespace Schemas { - /** - * Request payload for creating a new organization - */ - export interface CreateOrganizationRequest { - /** - * Core organization information for the new tenant - */ - organization_detail?: { + export interface ActiveAnnouncementResponse { + announcement: { + id: string; // uuid + type: AnnouncementType; + message: AnnouncementMessage; + link_url: string; + list_version: string; + effective_date: string; // date + window_days: number; + status: "draft" | "published" | "archived"; + created_at: string; // date-time /** - * Display name for the organization - * example: - * epilot + * Internal caller that created the announcement (not returned to customers) */ - name: string; + created_by?: string; + updated_at: string; // date-time + published_at?: string; // date-time /** - * Primary contact email for the organization - * example: - * epilot@epilot.cloud + * Internal caller that published the announcement (not returned to customers) */ - email_address?: string; + published_by?: string; /** - * Organization type classification. - * - Vendor: Primary organization type for epilot customers - * - Partner: Partner organization type - * - * example: - * Vendor + * published_at + window_days */ - type: string; + expires_at?: string; // date-time + archived_at?: string; // date-time + archived_by?: string; /** - * Whether this is a production or sandbox environment. - * Sandbox organizations are for testing and linked to a production org. - * + * When the daily job wrote expired/superseded records after the window closed */ - organization_use?: "Production" | "Sandbox"; - /** - * The ULID of the pricing tier to assign to this organization. - * Determines feature access and billing. - * - * example: - * 01GEKHZHSN19KK10ZS92Y3WY9B - */ - pricing_tier_id: string; - }; + materialized_at?: string; // date-time + } | null; /** - * Initial owner user details. - * This user will receive an invitation email to complete registration - * and become the organization administrator. - * + * Whether the caller may acknowledge (organization:edit) */ - owner_user?: { - /** - * Full name of the owner user - * example: - * Ny Huynh - */ - full_name?: string; - /** - * Email address for the invitation (must be unique in the platform) - * example: - * ny.huynhthi@axonactive.com - */ - email_address: string; - }; + can_acknowledge: boolean; } - /** - * Data point metrics for a single organization, used for usage tracking and billing - */ - export interface DataPoint { + export interface Announcement { + id: string; // uuid + type: AnnouncementType; + message: AnnouncementMessage; + link_url: string; + list_version: string; + effective_date: string; // date + window_days: number; + status: "draft" | "published" | "archived"; + created_at: string; // date-time + /** + * Internal caller that created the announcement (not returned to customers) + */ + created_by?: string; + updated_at: string; // date-time + published_at?: string; // date-time + /** + * Internal caller that published the announcement (not returned to customers) + */ + published_by?: string; + /** + * published_at + window_days + */ + expires_at?: string; // date-time + archived_at?: string; // date-time + archived_by?: string; + /** + * When the daily job wrote expired/superseded records after the window closed + */ + materialized_at?: string; // date-time + } + export interface AnnouncementMessage { /** - * The organization ID (numeric format) * example: - * 206801 + * Wir haben unsere Liste der Subdienstleister aktualisiert (gültig ab 01.11.2026). Bitte prüfe die Änderungen. */ - id?: number; + de: string; /** - * Maximum number of customer entities recorded in the previous month. - * Used as a high-water mark for billing calculations. - * * example: - * 10 + * We have updated our list of subprocessors (effective 01.11.2026). Please review the changes. */ - max_customer?: number; + en?: string; + } + export interface AnnouncementOrganizationStatus { + announcement_id: string; + org_id: string; + org_name?: string; + status: AnnouncementOrganizationStatusValue; + acknowledged_by?: { + user_id: string; + name?: string; + email?: string; + }; + acknowledged_at?: string; // date-time /** - * Current count of customer entities in the organization. - * Represents the actual usage at the time of the query. - * * example: - * 10 + * 360 */ - actual_customer?: number; + portal?: string; + list_version: string; + /** + * When the record was written (acknowledgement or materialization by the daily job) + */ + recorded_at?: string; // date-time } - /** - * List of data point metrics for all organizations - */ - export type DataPointsResponse = /* Data point metrics for a single organization, used for usage tracking and billing */ DataPoint[]; + export type AnnouncementOrganizationStatusValue = "acknowledged" | "pending" | "expired" | "superseded"; + export type AnnouncementType = "subprocessor_change"; /** * Metadata for a single feature flag toggle */ @@ -161,172 +175,6 @@ declare namespace Components { */ feature_flags: /* Metadata for a single feature flag toggle */ FeatureFlagMetadata[]; } - /** - * Response containing a list of HubSpot companies matching the search criteria - */ - export interface HubspotCompaniesResponse { - /** - * List of HubSpot companies matching the query - */ - results?: /** - * Represents a company record from HubSpot CRM synchronized to the epilot data warehouse. - * Used for CRM integration and organization mapping. - * - */ - HubspotCompany[]; - /** - * Total number of companies in the response - * example: - * 10 - */ - total?: number; - } - /** - * Represents a company record from HubSpot CRM synchronized to the epilot data warehouse. - * Used for CRM integration and organization mapping. - * - */ - export interface HubspotCompany { - /** - * The unique HubSpot company identifier - * example: - * 12345678901 - */ - company_id?: string; - /** - * The display name of the company in HubSpot - * example: - * Acme Corp - */ - company_name?: string | null; - /** - * The primary web domain associated with the company - * example: - * acme.com - */ - domain?: string | null; - } - /** - * HubSpot company data associated with an epilot organization. - * Contains business metrics and CRM properties synced from HubSpot. - * - */ - export interface HubspotOrganizationData { - /** - * The unique HubSpot company identifier - * example: - * 5278308807 - */ - company_id?: string; - /** - * The display name of the company in HubSpot - * example: - * Stadtwerke Meerbusch Willich Netz - */ - company_name?: string | null; - /** - * The primary web domain associated with the company - * example: - * stadtwerke-service.de - */ - domain?: string | null; - /** - * Current Monthly Recurring Revenue - * example: - * 11033.33 - */ - current_mrr?: string | null; - /** - * Potential Monthly Recurring Revenue - * example: - * 20000 - */ - potential_mrr?: string | null; - /** - * Company size classification - * example: - * Large - */ - company_size?: string | null; - /** - * Current pricing tier - * example: - * Professional - */ - pricing_tier?: string | null; - /** - * HubSpot lifecycle stage - * example: - * customer - */ - lifecyclestage?: string | null; - /** - * Industry classification - * example: - * Energiedienstleister - */ - industry?: string | null; - /** - * Number of employees - * example: - * 200 - */ - numberofemployees?: string | null; - /** - * epilot customer number - * example: - * 10031 - */ - customer_number?: string | null; - /** - * Number of active users - * example: - * 34 - */ - no_of_users?: string | null; - /** - * User activation rate - * example: - * 0.647059 - */ - activation_rate?: string | null; - /** - * List of active features - * example: - * Installer Portal - */ - active_features?: string | null; - /** - * Implemented use cases - * example: - * GRID: Installations (MGA);GRID: House Connections - */ - usecases_implemented?: string | null; - /** - * Current buyer's journey stage - * example: - * Expansion-Ready - */ - buyer_journey?: string | null; - /** - * Company country - * example: - * Germany - */ - country?: string | null; - /** - * Company city - * example: - * Willich - */ - city?: string | null; - /** - * Full HubSpot properties object with all available fields - */ - properties?: { - [name: string]: any; - }; - } /** * Internationalized string with a translation key and optional default value */ @@ -344,124 +192,6 @@ declare namespace Components { */ default_value?: string; } - /** - * Extended organization object with internal-only fields. - * - * Includes all fields from the base Organization schema plus additional - * internal metadata used for platform administration and support. - * - */ - export interface InternalOrganization { - id?: /** - * Unique identifier for an organization (tenant) in the epilot platform - * example: - * 739224 - */ - OrganizationId; - /** - * Organization name - * example: - * Epilot - */ - name?: string | null; - /** - * Organization contact email - * example: - * someone@epilot.cloud - */ - email?: string | null; - /** - * Organization contact phone - * example: - * +49123123123 - */ - phone?: string | null; - /** - * Organization website - * example: - * https://epilot.cloud - */ - website?: string | null; - /** - * Organization postal address - */ - address?: { - [name: string]: any; - country?: string | null; - city?: string | null; - postal_code?: string | null; - street?: string | null; - street_number?: string | null; - }; - /** - * Indicates whether this is a production or sandbox organization. - * Sandbox organizations are linked to a parent production organization for testing purposes. - * - */ - organization_use?: "Production" | "Sandbox"; - /** - * The ID of the parent production organization. - * Only set for sandbox organizations to link them to their production counterpart. - * - */ - parent_production_org_id?: string | null; - /** - * Organization created date - */ - created_date?: string; // date-time - /** - * Organization logo URL - * example: - * https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png - */ - logo_url?: string | null; - /** - * Organization logo thumbnail URL - * example: - * https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png - */ - logo_thumbnail_url?: string | null; - /** - * Default email signature of organization - * example: - *

Thanks

- */ - signature?: string | null; - /** - * example: - * false - */ - is_unlicensed_org?: boolean | null; - /** - * Organization style setting (e.g. font setting) - */ - style?: { - [name: string]: any; - } | null; - type?: "Vendor" | "Partner"; - /** - * example: - * EPI - */ - symbol?: string | null; - /** - * Deprecated - Please use pricing tier api - * example: - * professional - */ - pricing_tier?: string | null; - /** - * example: - * 50 - */ - free_user_limit?: number | null; - /** - * Labels/tags associated with the organization for categorization and filtering. - * Used internally to classify organizations (e.g., "enterprise", "beta-tester", "test org"). - * - */ - tags?: string[] | null; - } /** * Represents an epilot organization (tenant). * @@ -512,11 +242,12 @@ declare namespace Components { street_number?: string | null; }; /** - * Indicates whether this is a production or sandbox organization. + * Indicates whether this is a production, sandbox or preview organization. * Sandbox organizations are linked to a parent production organization for testing purposes. + * Preview organizations are temporary copies of a production organization. * */ - organization_use?: "Production" | "Sandbox"; + organization_use?: "Production" | "Sandbox" | "Preview"; /** * The ID of the parent production organization. * Only set for sandbox organizations to link them to their production counterpart. @@ -580,109 +311,12 @@ declare namespace Components { */ tags?: string[] | null; } - /** - * Records the cleanup status reported by a specific service for an organization. - * Each service that stores organization data reports its cleanup operations here. - * - */ - export interface OrganizationCleanupStatus { - org_id: /** - * Unique identifier for an organization (tenant) in the epilot platform - * example: - * 739224 - */ - OrganizationId; - /** - * Name of the service reporting its cleanup status - * example: - * organization-api - */ - service_name: string; - /** - * List of cleanup operations performed by the service. - * Each operation describes what action was taken on which resource. - * - * example: - * [ - * { - * "action": "Delete", - * "resource": "OrgTable", - * "extra_info": "pk: ORG#739224" - * }, - * { - * "action": "Archive", - * "resource": "s3://my-bucket", - * "extra_info": "my-bucket/my-key.txt" - * } - * ] - */ - operations?: { - [name: string]: any; - /** - * The type of cleanup action performed. - * Common values: Delete, Archive, Purge - * - * example: - * Delete - */ - action?: string; - /** - * The resource that was cleaned up. - * Can be a table name, S3 bucket, or other resource identifier. - * - * example: - * OrgTable - */ - resource?: string; - /** - * Additional context about the cleanup operation - */ - extra_info?: /* Additional context about the cleanup operation */ string | number | boolean | { - [name: string]: any; - }[] | { - [name: string]: any; - }; - }[]; - } /** * Unique identifier for an organization (tenant) in the epilot platform * example: * 739224 */ export type OrganizationId = string; - /** - * Represents an organization that has been marked for deletion and requires cleanup. - * Contains metadata about the deletion request. - * - */ - export interface OrganizationToCleanup { - org_id: /** - * Unique identifier for an organization (tenant) in the epilot platform - * example: - * 739224 - */ - OrganizationId; - /** - * Timestamp when the organization was marked for deletion - * example: - * 2021-06-01T00:00:00Z - */ - deleted_at: string; // date-time - /** - * User ID of the person who initiated the deletion - * example: - * 123456 - */ - deleted_by: string; - } - /** - * A unique key identifying an organization setting. - * Common setting keys include: double_opt_in, email_tracking, default_language, workflow_notifications - * - * example: - * double_opt_in - */ - export type SettingKey = string; /** * A key-value map of all organization settings. * Keys are setting identifiers and values can be any valid JSON type. @@ -734,7 +368,7 @@ declare namespace Components { /** * The rule type discriminator */ - type: "always" | "never" | "advanced_mode" | "pricing_tier" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or"; + type: "always" | "never" | "advanced_mode" | "pricing_tier" | "pricing_tier_class" | "production_org" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or"; /** * Setting key for pricing_tier and setting_enabled rules */ @@ -743,6 +377,15 @@ declare namespace Components { * Flag name for feature_flag and flag_enabled rules */ flag_name?: string; + /** + * Pricing tier class names matched (word-boundary, case-insensitive) for pricing_tier_class rules + * example: + * [ + * "enterprise", + * "professional" + * ] + */ + pricing_tier_classes?: string[]; /** * Permission action for permission rules */ @@ -770,6 +413,33 @@ declare namespace Components { } } declare namespace Paths { + namespace AcknowledgeAnnouncement { + namespace Parameters { + export type AnnouncementId = string; + } + export interface PathParameters { + announcement_id: Parameters.AnnouncementId; + } + export interface RequestBody { + /** + * Portal the acknowledgement was given in + * example: + * 360 + */ + portal?: string; + } + namespace Responses { + export type $200 = Components.Schemas.AnnouncementOrganizationStatus; + export interface $401 { + } + export interface $403 { + } + export interface $404 { + } + export interface $409 { + } + } + } namespace DeleteSettingsValue { namespace Parameters { export type Key = string; @@ -795,6 +465,13 @@ declare namespace Paths { } } } + namespace GetActiveAnnouncement { + namespace Responses { + export type $200 = Components.Schemas.ActiveAnnouncementResponse; + export interface $401 { + } + } + } namespace GetCurrentOrganization { namespace Responses { export type $200 = /** @@ -892,6 +569,50 @@ declare namespace Paths { } } } + namespace NotifySettingsValueUpdated { + namespace Parameters { + export type Key = string; + export type OrgId = /** + * Unique identifier for an organization (tenant) in the epilot platform + * example: + * 739224 + */ + Components.Schemas.OrganizationId; + } + export interface PathParameters { + org_id: Parameters.OrgId; + key: Parameters.Key; + } + export interface RequestBody { + /** + * Email of the staff user who changed the setting + */ + user_email?: string; + /** + * Organization display name + */ + org_name?: string; + value?: /** + * The value of an organization setting. + * Can be any valid JSON type: string, number, boolean, array, or object. + * + * example: + * { + * "enabled": true + * } + */ + Components.Schemas.SettingsValue; + } + namespace Responses { + export interface $200 { + delivered?: boolean; + } + export interface $401 { + } + export interface $403 { + } + } + } namespace PutSettingsValue { namespace Parameters { export type Key = string; @@ -1083,7 +804,19 @@ export interface OperationMethods { config?: AxiosRequestConfig ): OperationResponse /** - * getOrganizationFeatureSettings - Get organization feature settings + * notifySettingsValueUpdated - notifySettingsValueUpdated + * + * Sends an internal notification that an organization setting was updated. + */ + 'notifySettingsValueUpdated'( + parameters?: Parameters | null, + data?: Paths.NotifySettingsValueUpdated.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getOrganizationFeatureSettings - getOrganizationFeatureSettings + * + * Get organization feature settings. * * Returns feature flag metadata for the organization UI. * @@ -1097,15 +830,51 @@ export interface OperationMethods { config?: AxiosRequestConfig ): OperationResponse /** - * getFeatureSettings - Get platform configuration metadata + * getFeatureSettings - getFeatureSettings + * + * Get platform configuration metadata. * * Deprecated: use /v2/organization/feature-settings instead. + * */ 'getFeatureSettings'( parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * getActiveAnnouncement - getActiveAnnouncement + * + * Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the + * caller's organization. + * + * Only the newest published announcement whose acknowledgement window is still open is returned, and + * only if the organization is eligible (created before the announcement was published) and has not + * acknowledged it yet. Otherwise `announcement` is `null`. + * + * `can_acknowledge` tells whether the caller may acknowledge (requires `organization:edit`). + * + */ + 'getActiveAnnouncement'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * acknowledgeAnnouncement - acknowledgeAnnouncement + * + * Acknowledges an announcement on behalf of the caller's organization. Only org admins + * (`organization:edit`) may acknowledge. + * + * One acknowledgement per organization: the first acknowledgement wins. Acknowledging an already + * acknowledged announcement returns the existing record. + * + */ + 'acknowledgeAnnouncement'( + parameters?: Parameters | null, + data?: Paths.AcknowledgeAnnouncement.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse } export interface PathsDictionary { @@ -1219,9 +988,23 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v2/organization/{org_id}/settings/{key}/notifications']: { + /** + * notifySettingsValueUpdated - notifySettingsValueUpdated + * + * Sends an internal notification that an organization setting was updated. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.NotifySettingsValueUpdated.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } ['/v2/organization/feature-settings']: { /** - * getOrganizationFeatureSettings - Get organization feature settings + * getOrganizationFeatureSettings - getOrganizationFeatureSettings + * + * Get organization feature settings. * * Returns feature flag metadata for the organization UI. * @@ -1237,9 +1020,12 @@ export interface PathsDictionary { } ['/v2/feature-settings']: { /** - * getFeatureSettings - Get platform configuration metadata + * getFeatureSettings - getFeatureSettings + * + * Get platform configuration metadata. * * Deprecated: use /v2/organization/feature-settings instead. + * */ 'get'( parameters?: Parameters | null, @@ -1247,26 +1033,59 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v2/organization/announcements/active']: { + /** + * getActiveAnnouncement - getActiveAnnouncement + * + * Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the + * caller's organization. + * + * Only the newest published announcement whose acknowledgement window is still open is returned, and + * only if the organization is eligible (created before the announcement was published) and has not + * acknowledged it yet. Otherwise `announcement` is `null`. + * + * `can_acknowledge` tells whether the caller may acknowledge (requires `organization:edit`). + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v2/organization/announcements/{announcement_id}/acknowledge']: { + /** + * acknowledgeAnnouncement - acknowledgeAnnouncement + * + * Acknowledges an announcement on behalf of the caller's organization. Only org admins + * (`organization:edit`) may acknowledge. + * + * One acknowledgement per organization: the first acknowledgement wins. Acknowledging an already + * acknowledged announcement returns the existing record. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.AcknowledgeAnnouncement.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } } export type Client = OpenAPIClient -export type CreateOrganizationRequest = Components.Schemas.CreateOrganizationRequest; -export type DataPoint = Components.Schemas.DataPoint; -export type DataPointsResponse = Components.Schemas.DataPointsResponse; +export type ActiveAnnouncementResponse = Components.Schemas.ActiveAnnouncementResponse; +export type Announcement = Components.Schemas.Announcement; +export type AnnouncementMessage = Components.Schemas.AnnouncementMessage; +export type AnnouncementOrganizationStatus = Components.Schemas.AnnouncementOrganizationStatus; +export type AnnouncementOrganizationStatusValue = Components.Schemas.AnnouncementOrganizationStatusValue; +export type AnnouncementType = Components.Schemas.AnnouncementType; export type FeatureFlagMetadata = Components.Schemas.FeatureFlagMetadata; export type FeatureSettings = Components.Schemas.FeatureSettings; -export type HubspotCompaniesResponse = Components.Schemas.HubspotCompaniesResponse; -export type HubspotCompany = Components.Schemas.HubspotCompany; -export type HubspotOrganizationData = Components.Schemas.HubspotOrganizationData; export type I18nString = Components.Schemas.I18nString; -export type InternalOrganization = Components.Schemas.InternalOrganization; export type Organization = Components.Schemas.Organization; -export type OrganizationCleanupStatus = Components.Schemas.OrganizationCleanupStatus; export type OrganizationId = Components.Schemas.OrganizationId; -export type OrganizationToCleanup = Components.Schemas.OrganizationToCleanup; -export type SettingKey = Components.Schemas.SettingKey; export type Settings = Components.Schemas.Settings; export type SettingsValue = Components.Schemas.SettingsValue; export type VisibilityRule = Components.Schemas.VisibilityRule; diff --git a/clients/organization-client/src/openapi.json b/clients/organization-client/src/openapi.json index 790926f6d..5550b8d9b 100644 --- a/clients/organization-client/src/openapi.json +++ b/clients/organization-client/src/openapi.json @@ -2,33 +2,25 @@ "openapi": "3.0.3", "info": { "title": "Organization API", - "description": "The Organization API provides endpoints for managing epilot tenant organizations.\n\n## Overview\n\nThis API allows you to:\n- **Retrieve organization details** - Get information about the current organization or a specific organization by ID\n- **Update organization profiles** - Modify organization name, contact information, address, and branding assets\n- **Manage organization settings** - Configure organization-wide settings such as feature flags and preferences\n- **Branding configuration** - Set organization logos, email signatures, and style settings\n\n## Organization Types\n\nOrganizations can be configured as:\n- **Production** - Live production environments for real business operations\n- **Sandbox** - Test environments linked to a parent production organization\n", + "description": "The Organization API provides endpoints for managing epilot tenant organizations.\n\n## Overview\n\nThis API allows you to:\n- **Retrieve organization details** - Get information about the current organization or a specific organization by ID\n- **Update organization profiles** - Modify organization name, contact information, address, and branding assets\n- **Manage organization settings** - Configure organization-wide settings such as feature flags and preferences\n- **Branding configuration** - Set organization logos, email signatures, and style settings\n\n## Organization Types\n\nOrganizations can be configured as:\n- **Production** - Live production environments for real business operations\n- **Sandbox** - Test environments linked to a parent production organization\n- **Preview** - Temporary copies of a production organization, used to preview configuration changes\n", "version": "1.1.0" }, "tags": [ { "name": "Organization", - "description": "Endpoints for managing organization profiles and information.\n\nOrganizations represent tenant accounts in the epilot platform. Each organization has:\n- Basic profile information (name, contact details, address)\n- Branding assets (logo, email signature)\n- Configuration settings\n- Usage type (Production or Sandbox)\n" + "description": "Endpoints for managing organization profiles and information.\n\nOrganizations represent tenant accounts in the epilot platform. Each organization has:\n- Basic profile information (name, contact details, address)\n- Branding assets (logo, email signature)\n- Configuration settings\n- Usage type (Production, Sandbox or Preview)\n" }, { "name": "Organization Settings", "description": "Endpoints for managing organization-wide configuration settings.\n\nSettings are stored as key-value pairs and can include:\n- Feature flags (e.g., double_opt_in)\n- Integration configurations\n- Platform preferences\n\nSettings values can be strings, numbers, booleans, arrays, or objects.\n" }, - { - "name": "Cleanup", - "description": "Internal endpoints for organization cleanup operations.\n\nThese endpoints are used by internal services to manage the deletion and cleanup\nof organization data across the platform. They track cleanup status per service\nto ensure complete data removal.\n" - }, - { - "name": "Internal", - "description": "Internal endpoints for platform administration.\n\nThese endpoints are not part of the public API and are used by internal epilot\nservices for administrative operations such as organization provisioning,\ndata aggregation, and cross-organization queries.\n" - }, - { - "name": "HubSpot", - "description": "Internal endpoints for HubSpot CRM integration.\n\nThese endpoints provide access to HubSpot company data synchronized with the\nepilot data warehouse for organization mapping and CRM integration purposes.\n" - }, { "name": "Feature Settings", "description": "Feature flag metadata for the organization settings page.\n\nThis metadata is used by frontend applications to render feature toggles\nwithout hardcoding titles, descriptions, badges, and visibility rules in the UI.\n" + }, + { + "name": "Announcements", + "description": "Platform announcements shown as a non-blocking portal banner, e.g. subprocessor change notices.\n\nAnnouncements are managed internally (admin portal) and acknowledged by organization admins.\nEach organization acknowledges an announcement at most once (first acknowledgement wins).\n" } ], "security": [ @@ -40,6 +32,11 @@ "/v2/organization/current": { "get": { "operationId": "getCurrentOrganization", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "summary": "getCurrentOrganization", "description": "Retrieves the organization associated with the authenticated user's current session.\n\nThis endpoint is useful for fetching organization details without knowing the organization ID,\nas it automatically resolves the organization from the authentication token.\n", "tags": [ @@ -86,6 +83,11 @@ "/v2/organization/{org_id}": { "get": { "operationId": "getOrganization", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "summary": "getOrganization", "description": "Retrieves detailed information about a specific organization by its unique identifier.\n\nReturns the organization's profile information including name, contact details,\naddress, branding assets, and configuration.\n", "tags": [ @@ -144,6 +146,11 @@ }, "patch": { "operationId": "updateOrganization", + "x-epilot-permissions": [ + { + "action": "organization:edit" + } + ], "summary": "updateOrganization", "description": "Updates an organization's profile information.\n\nThis endpoint supports partial updates - only the fields provided in the request body\nwill be modified. Fields not included in the request will retain their current values.\n\nCommon use cases:\n- Updating contact information (email, phone, website)\n- Changing the organization name\n- Updating the postal address\n- Setting or updating branding assets (logo_url, logo_thumbnail_url)\n- Configuring the default email signature\n", "tags": [ @@ -215,6 +222,11 @@ "/v2/organization/{org_id}/settings": { "get": { "operationId": "getSettings", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "summary": "getSettings", "description": "Retrieves all configuration settings for an organization.\n\nReturns a key-value map of all settings configured for the organization.\nSettings control various platform features and behaviors including:\n- Feature flags (e.g., double_opt_in, email_tracking)\n- Integration configurations\n- UI preferences\n- Workflow settings\n", "tags": [ @@ -275,6 +287,14 @@ "/v2/organization/{org_id}/settings/{key}": { "put": { "operationId": "putSettingsValue", + "x-epilot-agent": { + "expose": false + }, + "x-epilot-permissions": [ + { + "action": "organization:settings" + } + ], "summary": "putSettingsValue", "description": "Creates or updates a specific organization setting identified by its key.\n\nThe setting value can be any valid JSON type:\n- String (e.g., \"en\", \"production\")\n- Number (e.g., 100, 3.14)\n- Boolean (e.g., true, false)\n- Object (e.g., {\"enabled\": true, \"threshold\": 50})\n- Array (e.g., [\"email\", \"sms\", \"push\"])\n\nIf the setting key does not exist, it will be created. If it exists, its value\nwill be replaced with the new value.\n", "tags": [ @@ -362,6 +382,14 @@ }, "delete": { "operationId": "deleteSettingsValue", + "x-epilot-agent": { + "expose": false + }, + "x-epilot-permissions": [ + { + "action": "organization:settings" + } + ], "summary": "deleteSettingsValue", "description": "Removes a specific organization setting identified by its key.\n\nAfter deletion, the setting will no longer appear in the organization's settings\nand any features relying on this setting will use their default behavior.\n", "tags": [ @@ -404,11 +432,99 @@ } } }, + "/v2/organization/{org_id}/settings/{key}/notifications": { + "post": { + "operationId": "notifySettingsValueUpdated", + "x-epilot-permissions": [ + { + "action": "organization:settings" + } + ], + "summary": "notifySettingsValueUpdated", + "description": "Sends an internal notification that an organization setting was updated.", + "tags": [ + "Organization Settings" + ], + "parameters": [ + { + "in": "path", + "name": "org_id", + "schema": { + "$ref": "#/components/schemas/OrganizationId" + }, + "required": true, + "description": "The unique identifier of the organization" + }, + { + "in": "path", + "name": "key", + "schema": { + "type": "string" + }, + "required": true, + "description": "The setting key that was updated", + "example": "360_features" + } + ], + "requestBody": { + "description": "Notification payload for the updated setting", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user_email": { + "type": "string", + "description": "Email of the staff user who changed the setting" + }, + "org_name": { + "type": "string", + "description": "Organization display name" + }, + "value": { + "$ref": "#/components/schemas/SettingsValue" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Notification processed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "delivered": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + }, + "403": { + "description": "Forbidden - User does not have permission to notify for organization settings" + } + } + } + }, "/v2/organization/feature-settings": { "get": { "operationId": "getOrganizationFeatureSettings", - "summary": "Get organization feature settings", - "description": "Returns feature flag metadata for the organization UI.\n\nThis data is static (identical for all organizations) and changes infrequently.\nClients should cache responses using the Cache-Control header.\n", + "x-epilot-permissions": [ + { + "access": "org" + } + ], + "summary": "getOrganizationFeatureSettings", + "description": "Get organization feature settings.\n\nReturns feature flag metadata for the organization UI.\n\nThis data is static (identical for all organizations) and changes infrequently.\nClients should cache responses using the Cache-Control header.\n", "tags": [ "Feature Settings" ], @@ -440,9 +556,14 @@ "/v2/feature-settings": { "get": { "operationId": "getFeatureSettings", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "deprecated": true, - "summary": "Get platform configuration metadata", - "description": "Deprecated: use /v2/organization/feature-settings instead.", + "summary": "getFeatureSettings", + "description": "Get platform configuration metadata.\n\nDeprecated: use /v2/organization/feature-settings instead.\n", "tags": [ "Feature Settings" ], @@ -462,6 +583,97 @@ } } } + }, + "/v2/organization/announcements/active": { + "get": { + "operationId": "getActiveAnnouncement", + "x-epilot-permissions": [ + { + "access": "org" + } + ], + "summary": "getActiveAnnouncement", + "description": "Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the\ncaller's organization.\n\nOnly the newest published announcement whose acknowledgement window is still open is returned, and\nonly if the organization is eligible (created before the announcement was published) and has not\nacknowledged it yet. Otherwise `announcement` is `null`.\n\n`can_acknowledge` tells whether the caller may acknowledge (requires `organization:edit`).\n", + "tags": [ + "Announcements" + ], + "responses": { + "200": { + "description": "Active announcement for the organization, or null", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActiveAnnouncementResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + } + } + } + }, + "/v2/organization/announcements/{announcement_id}/acknowledge": { + "post": { + "operationId": "acknowledgeAnnouncement", + "x-epilot-permissions": [ + { + "action": "organization:edit" + } + ], + "summary": "acknowledgeAnnouncement", + "description": "Acknowledges an announcement on behalf of the caller's organization. Only org admins\n(`organization:edit`) may acknowledge.\n\nOne acknowledgement per organization: the first acknowledgement wins. Acknowledging an already\nacknowledged announcement returns the existing record.\n", + "tags": [ + "Announcements" + ], + "parameters": [ + { + "$ref": "#/components/parameters/AnnouncementIdPathParam" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "portal": { + "type": "string", + "description": "Portal the acknowledgement was given in", + "example": "360" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Acknowledgement record of the organization (new or existing)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnnouncementOrganizationStatus" + } + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + }, + "403": { + "description": "Forbidden - Caller is missing the organization:edit permission" + }, + "404": { + "description": "Announcement not found or organization not eligible" + }, + "409": { + "description": "Announcement window closed, announcement superseded or archived" + } + } + } } }, "components": { @@ -473,6 +685,17 @@ "bearerFormat": "JWT" } }, + "parameters": { + "AnnouncementIdPathParam": { + "in": "path", + "name": "announcement_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Announcement id" + } + }, "schemas": { "OrganizationId": { "type": "string", @@ -539,10 +762,11 @@ }, "organization_use": { "type": "string", - "description": "Indicates whether this is a production or sandbox organization.\nSandbox organizations are linked to a parent production organization for testing purposes.\n", + "description": "Indicates whether this is a production, sandbox or preview organization.\nSandbox organizations are linked to a parent production organization for testing purposes.\nPreview organizations are temporary copies of a production organization.\n", "enum": [ "Production", - "Sandbox" + "Sandbox", + "Preview" ], "nullable": true }, @@ -626,19 +850,6 @@ } } }, - "InternalOrganization": { - "description": "Extended organization object with internal-only fields.\n\nIncludes all fields from the base Organization schema plus additional\ninternal metadata used for platform administration and support.\n", - "allOf": [ - { - "$ref": "#/components/schemas/Organization" - } - ] - }, - "SettingKey": { - "type": "string", - "description": "A unique key identifying an organization setting.\nCommon setting keys include: double_opt_in, email_tracking, default_language, workflow_notifications\n", - "example": "double_opt_in" - }, "Settings": { "type": "object", "description": "A key-value map of all organization settings.\nKeys are setting identifiers and values can be any valid JSON type.\n", @@ -682,357 +893,6 @@ "enabled": true } }, - "CreateOrganizationRequest": { - "type": "object", - "description": "Request payload for creating a new organization", - "properties": { - "organization_detail": { - "description": "Core organization information for the new tenant", - "type": "object", - "required": [ - "name", - "type", - "pricing_tier_id" - ], - "properties": { - "name": { - "description": "Display name for the organization", - "type": "string", - "example": "epilot" - }, - "email_address": { - "description": "Primary contact email for the organization", - "type": "string", - "example": "epilot@epilot.cloud" - }, - "type": { - "description": "Organization type classification.\n- Vendor: Primary organization type for epilot customers\n- Partner: Partner organization type\n", - "type": "string", - "example": "Vendor" - }, - "organization_use": { - "description": "Whether this is a production or sandbox environment.\nSandbox organizations are for testing and linked to a production org.\n", - "type": "string", - "enum": [ - "Production", - "Sandbox" - ] - }, - "pricing_tier_id": { - "description": "The ULID of the pricing tier to assign to this organization.\nDetermines feature access and billing.\n", - "type": "string", - "example": "01GEKHZHSN19KK10ZS92Y3WY9B" - } - } - }, - "owner_user": { - "description": "Initial owner user details.\nThis user will receive an invitation email to complete registration\nand become the organization administrator.\n", - "type": "object", - "required": [ - "email_address" - ], - "properties": { - "full_name": { - "description": "Full name of the owner user", - "type": "string", - "example": "Ny Huynh" - }, - "email_address": { - "description": "Email address for the invitation (must be unique in the platform)", - "type": "string", - "example": "ny.huynhthi@axonactive.com" - } - } - } - } - }, - "DataPointsResponse": { - "type": "array", - "description": "List of data point metrics for all organizations", - "items": { - "$ref": "#/components/schemas/DataPoint" - } - }, - "DataPoint": { - "type": "object", - "description": "Data point metrics for a single organization, used for usage tracking and billing", - "properties": { - "id": { - "description": "The organization ID (numeric format)", - "type": "number", - "example": 206801 - }, - "max_customer": { - "description": "Maximum number of customer entities recorded in the previous month.\nUsed as a high-water mark for billing calculations.\n", - "type": "number", - "example": 10 - }, - "actual_customer": { - "description": "Current count of customer entities in the organization.\nRepresents the actual usage at the time of the query.\n", - "type": "number", - "example": 10 - } - } - }, - "OrganizationToCleanup": { - "type": "object", - "description": "Represents an organization that has been marked for deletion and requires cleanup.\nContains metadata about the deletion request.\n", - "required": [ - "org_id", - "deleted_at", - "deleted_by" - ], - "properties": { - "org_id": { - "$ref": "#/components/schemas/OrganizationId" - }, - "deleted_at": { - "description": "Timestamp when the organization was marked for deletion", - "type": "string", - "format": "date-time", - "example": "2021-06-01T00:00:00Z" - }, - "deleted_by": { - "description": "User ID of the person who initiated the deletion", - "type": "string", - "example": "123456" - } - } - }, - "OrganizationCleanupStatus": { - "type": "object", - "description": "Records the cleanup status reported by a specific service for an organization.\nEach service that stores organization data reports its cleanup operations here.\n", - "required": [ - "org_id", - "service_name" - ], - "properties": { - "org_id": { - "$ref": "#/components/schemas/OrganizationId" - }, - "service_name": { - "description": "Name of the service reporting its cleanup status", - "type": "string", - "example": "organization-api" - }, - "operations": { - "description": "List of cleanup operations performed by the service.\nEach operation describes what action was taken on which resource.\n", - "type": "array", - "items": { - "type": "object", - "additionalProperties": true, - "properties": { - "action": { - "description": "The type of cleanup action performed.\nCommon values: Delete, Archive, Purge\n", - "type": "string", - "example": "Delete" - }, - "resource": { - "description": "The resource that was cleaned up.\nCan be a table name, S3 bucket, or other resource identifier.\n", - "type": "string", - "example": "OrgTable" - }, - "extra_info": { - "description": "Additional context about the cleanup operation", - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - { - "type": "object", - "additionalProperties": true - } - ] - } - } - }, - "example": [ - { - "action": "Delete", - "resource": "OrgTable", - "extra_info": "pk: ORG#739224" - }, - { - "action": "Archive", - "resource": "s3://my-bucket", - "extra_info": "my-bucket/my-key.txt" - } - ] - } - } - }, - "HubspotCompany": { - "type": "object", - "description": "Represents a company record from HubSpot CRM synchronized to the epilot data warehouse.\nUsed for CRM integration and organization mapping.\n", - "properties": { - "company_id": { - "type": "string", - "description": "The unique HubSpot company identifier", - "example": "12345678901" - }, - "company_name": { - "type": "string", - "nullable": true, - "description": "The display name of the company in HubSpot", - "example": "Acme Corp" - }, - "domain": { - "type": "string", - "nullable": true, - "description": "The primary web domain associated with the company", - "example": "acme.com" - } - } - }, - "HubspotCompaniesResponse": { - "type": "object", - "description": "Response containing a list of HubSpot companies matching the search criteria", - "properties": { - "results": { - "type": "array", - "description": "List of HubSpot companies matching the query", - "items": { - "$ref": "#/components/schemas/HubspotCompany" - } - }, - "total": { - "type": "integer", - "description": "Total number of companies in the response", - "example": 10 - } - } - }, - "HubspotOrganizationData": { - "type": "object", - "description": "HubSpot company data associated with an epilot organization.\nContains business metrics and CRM properties synced from HubSpot.\n", - "properties": { - "company_id": { - "type": "string", - "description": "The unique HubSpot company identifier", - "example": "5278308807" - }, - "company_name": { - "type": "string", - "nullable": true, - "description": "The display name of the company in HubSpot", - "example": "Stadtwerke Meerbusch Willich Netz" - }, - "domain": { - "type": "string", - "nullable": true, - "description": "The primary web domain associated with the company", - "example": "stadtwerke-service.de" - }, - "current_mrr": { - "type": "string", - "nullable": true, - "description": "Current Monthly Recurring Revenue", - "example": "11033.33" - }, - "potential_mrr": { - "type": "string", - "nullable": true, - "description": "Potential Monthly Recurring Revenue", - "example": "20000" - }, - "company_size": { - "type": "string", - "nullable": true, - "description": "Company size classification", - "example": "Large" - }, - "pricing_tier": { - "type": "string", - "nullable": true, - "description": "Current pricing tier", - "example": "Professional" - }, - "lifecyclestage": { - "type": "string", - "nullable": true, - "description": "HubSpot lifecycle stage", - "example": "customer" - }, - "industry": { - "type": "string", - "nullable": true, - "description": "Industry classification", - "example": "Energiedienstleister" - }, - "numberofemployees": { - "type": "string", - "nullable": true, - "description": "Number of employees", - "example": "200" - }, - "customer_number": { - "type": "string", - "nullable": true, - "description": "epilot customer number", - "example": "10031" - }, - "no_of_users": { - "type": "string", - "nullable": true, - "description": "Number of active users", - "example": "34" - }, - "activation_rate": { - "type": "string", - "nullable": true, - "description": "User activation rate", - "example": "0.647059" - }, - "active_features": { - "type": "string", - "nullable": true, - "description": "List of active features", - "example": "Installer Portal" - }, - "usecases_implemented": { - "type": "string", - "nullable": true, - "description": "Implemented use cases", - "example": "GRID: Installations (MGA);GRID: House Connections" - }, - "buyer_journey": { - "type": "string", - "nullable": true, - "description": "Current buyer's journey stage", - "example": "Expansion-Ready" - }, - "country": { - "type": "string", - "nullable": true, - "description": "Company country", - "example": "Germany" - }, - "city": { - "type": "string", - "nullable": true, - "description": "Company city", - "example": "Willich" - }, - "properties": { - "type": "object", - "additionalProperties": true, - "description": "Full HubSpot properties object with all available fields" - } - } - }, "FeatureSettings": { "type": "object", "description": "Feature settings metadata served to frontend applications.", @@ -1145,6 +1005,8 @@ "never", "advanced_mode", "pricing_tier", + "pricing_tier_class", + "production_org", "feature_flag", "permission", "setting_enabled", @@ -1162,6 +1024,17 @@ "type": "string", "description": "Flag name for feature_flag and flag_enabled rules" }, + "pricing_tier_classes": { + "type": "array", + "description": "Pricing tier class names matched (word-boundary, case-insensitive) for pricing_tier_class rules", + "items": { + "type": "string" + }, + "example": [ + "enterprise", + "professional" + ] + }, "action": { "type": "string", "description": "Permission action for permission rules" @@ -1181,6 +1054,200 @@ } } } + }, + "AnnouncementType": { + "type": "string", + "enum": [ + "subprocessor_change" + ] + }, + "AnnouncementMessage": { + "type": "object", + "required": [ + "de" + ], + "properties": { + "de": { + "type": "string", + "minLength": 1, + "example": "Wir haben unsere Liste der Subdienstleister aktualisiert (gültig ab 01.11.2026). Bitte prüfe die Änderungen." + }, + "en": { + "type": "string", + "example": "We have updated our list of subprocessors (effective 01.11.2026). Please review the changes." + } + } + }, + "Announcement": { + "type": "object", + "required": [ + "id", + "type", + "message", + "link_url", + "list_version", + "effective_date", + "window_days", + "status", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "$ref": "#/components/schemas/AnnouncementType" + }, + "message": { + "$ref": "#/components/schemas/AnnouncementMessage" + }, + "link_url": { + "type": "string" + }, + "list_version": { + "type": "string" + }, + "effective_date": { + "type": "string", + "format": "date" + }, + "window_days": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published", + "archived" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string", + "description": "Internal caller that created the announcement (not returned to customers)" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "published_at": { + "type": "string", + "format": "date-time" + }, + "published_by": { + "type": "string", + "description": "Internal caller that published the announcement (not returned to customers)" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "published_at + window_days" + }, + "archived_at": { + "type": "string", + "format": "date-time" + }, + "archived_by": { + "type": "string" + }, + "materialized_at": { + "type": "string", + "format": "date-time", + "description": "When the daily job wrote expired/superseded records after the window closed" + } + } + }, + "AnnouncementOrganizationStatusValue": { + "type": "string", + "enum": [ + "acknowledged", + "pending", + "expired", + "superseded" + ] + }, + "AnnouncementOrganizationStatus": { + "type": "object", + "required": [ + "announcement_id", + "org_id", + "status", + "list_version" + ], + "properties": { + "announcement_id": { + "type": "string" + }, + "org_id": { + "type": "string" + }, + "org_name": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AnnouncementOrganizationStatusValue" + }, + "acknowledged_by": { + "type": "object", + "required": [ + "user_id" + ], + "properties": { + "user_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "acknowledged_at": { + "type": "string", + "format": "date-time" + }, + "portal": { + "type": "string", + "example": "360" + }, + "list_version": { + "type": "string" + }, + "recorded_at": { + "type": "string", + "format": "date-time", + "description": "When the record was written (acknowledgement or materialization by the daily job)" + } + } + }, + "ActiveAnnouncementResponse": { + "type": "object", + "required": [ + "announcement", + "can_acknowledge" + ], + "properties": { + "announcement": { + "allOf": [ + { + "$ref": "#/components/schemas/Announcement" + } + ], + "nullable": true + }, + "can_acknowledge": { + "type": "boolean", + "description": "Whether the caller may acknowledge (organization:edit)" + } + } } } }, diff --git a/packages/cli/definitions/configuration-hub.json b/packages/cli/definitions/configuration-hub.json index 6c5de17c1..198dfd256 100644 --- a/packages/cli/definitions/configuration-hub.json +++ b/packages/cli/definitions/configuration-hub.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "Configuration Hub API", - "description": "Lightweight index API for exploring epilot organization configurations.\n\nProvides a unified tree view across all config types. Returns summary metadata only —\nthe frontend calls individual epilot APIs directly (via @epilot/sdk) for full config payloads.\n\n## Source APIs per resource type\n\nEach resource type maps to a specific epilot API. The frontend should use the corresponding\n@epilot/sdk client to fetch full config details (e.g., for the side panel JSON view).\n\n| Resource Type | Source API | SDK Client |\n|---|---|---|\n| `journey` | journey.sls.epilot.io | `@epilot/sdk/journey` |\n| `automation_flow` | automation.sls.epilot.io | `@epilot/sdk/automation` |\n| `workflow_definition` | workflow-definition.sls.epilot.io | `@epilot/sdk/workflow-definition` |\n| `closing_reason` | workflow-definition.sls.epilot.io | `@epilot/sdk/workflow-definition` |\n| `flow_template` | workflow-definition.sls.epilot.io | `@epilot/sdk/workflow-definition` |\n| `schema` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `taxonomy` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `taxonomy_classification` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `emailtemplate` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `product` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `price` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `tax` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `coupon` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `file` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `document_template` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `webhook` | webhooks.sls.epilot.io | `@epilot/sdk/webhooks` |\n| `saved_view` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `dashboard` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `kanban` | kanban.sls.epilot.io | `@epilot/sdk/kanban` |\n| `role` | permissions.sls.epilot.io | `@epilot/sdk/permissions` |\n| `usergroup` | user.sls.epilot.io | `@epilot/sdk/user` |\n| `validation_rule` | entity.sls.epilot.io | `@epilot/sdk/validation-rules` |\n| `integration` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `app` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `designbuilder` | design-builder-api.sls.epilot.io | `@epilot/sdk/design` |\n| `notification_template` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `custom_variable` | entity.sls.epilot.io | `@epilot/sdk/template-variables` |\n| `environment_variable` | environments.sls.epilot.io | `@epilot/sdk/environments` |\n| `entity_mapping` | entity-mapping.sls.epilot.io | `@epilot/sdk/entity-mapping` |\n| `portal_config` | customer-portal.sls.epilot.io | `@epilot/sdk/customer-portal` |\n| `target` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `product_recommendation` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `access_token` | access-token.sls.epilot.io | `@epilot/sdk/access-token` |\n", + "description": "Lightweight index API for exploring epilot organization configurations.\n\nProvides a unified tree view across all config types. Returns summary metadata only —\nthe frontend calls individual epilot APIs directly (via @epilot/sdk) for full config payloads.\n\n## Source APIs per resource type\n\nEach resource type maps to a specific epilot API. The frontend should use the corresponding\n@epilot/sdk client to fetch full config details (e.g., for the side panel JSON view).\n\n| Resource Type | Source API | SDK Client |\n|---|---|---|\n| `journey` | journey.sls.epilot.io | `@epilot/sdk/journey` |\n| `datasource` | epilot360-native-journeys.sls.epilot.io | – (plain HTTP client) |\n| `automation_flow` | automation.sls.epilot.io | `@epilot/sdk/automation` |\n| `workflow_definition` | workflow-definition.sls.epilot.io | `@epilot/sdk/workflow-definition` |\n| `closing_reason` | workflow-definition.sls.epilot.io | `@epilot/sdk/workflow-definition` |\n| `flow_template` | workflow-definition.sls.epilot.io | `@epilot/sdk/workflow-definition` |\n| `schema` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `taxonomy` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `taxonomy_classification` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `emailtemplate` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `product` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `price` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `tax` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `coupon` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `file` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `document_template` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `webhook` | webhooks.sls.epilot.io | `@epilot/sdk/webhooks` |\n| `saved_view` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `dashboard` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `kanban` | kanban.sls.epilot.io | `@epilot/sdk/kanban` |\n| `role` | permissions.sls.epilot.io | `@epilot/sdk/permissions` |\n| `usergroup` | user.sls.epilot.io | `@epilot/sdk/user` |\n| `validation_rule` | entity.sls.epilot.io | `@epilot/sdk/validation-rules` |\n| `integration` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `app` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `designbuilder` | design-builder-api.sls.epilot.io | `@epilot/sdk/design` |\n| `notification_template` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `custom_variable` | entity.sls.epilot.io | `@epilot/sdk/template-variables` |\n| `environment_variable` | environments.sls.epilot.io | `@epilot/sdk/environments` |\n| `entity_mapping` | entity-mapping.sls.epilot.io | `@epilot/sdk/entity-mapping` |\n| `portal_config` | customer-portal.sls.epilot.io | `@epilot/sdk/customer-portal` |\n| `target` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `product_recommendation` | entity.sls.epilot.io | `@epilot/sdk/entity` |\n| `access_token` | access-token.sls.epilot.io | `@epilot/sdk/access-token` |\n| `email_signature` | email-settings.sls.epilot.io | `@epilot/sdk/email-settings` |\n| `shared_inbox` | email-settings.sls.epilot.io | `@epilot/sdk/email-settings` |\n| `org_setting` | organization-v2.sls.epilot.io | `@epilot/sdk/organization` |\n", "version": "0.2.0" }, "servers": [ @@ -35,12 +35,25 @@ { "name": "Compare", "description": "Cross-org configuration comparison, match suggestions, and lineage operations" + }, + { + "name": "Health", + "description": "Configuration health scans, reports, coverage requests, cleanup and ignored findings" + }, + { + "name": "Previews", + "description": "Preview orgs — temporary copies of an org's configuration" } ], "paths": { "/v1/configs/types": { "get": { "operationId": "listConfigTypes", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "listConfigTypes", "description": "Returns 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 then call `listConfigs` separately\nfor each type it wants to load.\n", "tags": [ @@ -74,6 +87,11 @@ "/v1/configs/{type}": { "get": { "operationId": "listConfigs", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "listConfigs", "description": "List 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\nSupports offset-based pagination via `from` and `size` parameters.\n", "tags": [ @@ -181,6 +199,11 @@ "/v1/configs/{type}/{id}/dependencies": { "get": { "operationId": "getConfigDependencies", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "getConfigDependencies", "description": "Get configs that are referenced by the given config.\nUsed to render children when expanding a config node in the tree.\n\nResolves dependencies by fetching the config payload server-side and scanning\nfor references (UUIDs, source IDs, slug-based references).\n", "tags": [ @@ -227,6 +250,11 @@ "/v1/configs/{type}/{id}/used_by": { "get": { "operationId": "getConfigUsedBy", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "getConfigUsedBy", "description": "Get configs that reference the given config (reverse dependencies).\nScans the indexed config items for references to this config's ID or aliases.\n", "tags": [ @@ -257,6 +285,11 @@ "/v1/configs/index": { "get": { "operationId": "getIndex", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "getIndex", "description": "Return 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", "tags": [ @@ -276,9 +309,335 @@ } } }, + "/v1/configs/health:scan": { + "post": { + "operationId": "startHealthScan", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "startHealthScan", + "description": "Start 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,\nautomation→template/webhook/workflow, workflow→automation, …) and\nrecords a finding for every reference that resolves to a missing or\ninactive config, plus configs that are listed but unreadable.\n\nRequires a built config index (422 otherwise). Poll `getHealthReport`\nfor status and results.\n", + "tags": [ + "Health" + ], + "responses": { + "202": { + "description": "Scan started (or already running)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthScanStartResponse" + } + } + } + }, + "422": { + "description": "Config index not built yet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthScanStartResponse" + } + } + } + } + } + } + }, + "/v1/configs/health": { + "get": { + "operationId": "getHealthReport", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getHealthReport", + "description": "Return 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).\nFindings carry `first_seen`/`last_seen` so the UI can correlate new\nfindings with recent imports and installs.\n", + "tags": [ + "Health" + ], + "responses": { + "200": { + "description": "Current health report", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthReportResponse" + } + } + } + } + } + } + }, + "/v1/configs/health:coverage-request": { + "post": { + "operationId": "requestHealthCoverage", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "requestHealthCoverage", + "description": "Send 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 for the stage.\n", + "tags": [ + "Health" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "maxLength": 2000 + } + } + } + } + } + }, + "responses": { + "202": { + "description": "Request sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "sent", + "failed" + ] + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Empty message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "503": { + "description": "No Slack webhook configured for this stage", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v1/configs/health:cleanup": { + "post": { + "operationId": "startHealthCleanup", + "x-epilot-permissions": [ + { + "action": "configuration_hub:delete" + } + ], + "summary": "startHealthCleanup", + "description": "Remove 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 a deleted\njourney (they can never fire), and the journey attached to a workflow\ntask when that journey is deleted (the task itself stays). Only\nfindings flagged `cleanable` are touched; live wiring — an action's\nemail template, a task's automation, a webhook target — is never\nmodified, because removing it would change what runs. Without\na body every cleanable finding is cleaned; `finding_ids` narrows the\nset (e.g. one row's Clean up button). Fire-and-forget: poll\n`getHealthReport` — status `cleaning` until done.\n", + "tags": [ + "Health" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "finding_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "202": { + "description": "Cleanup started", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "cleaning", + "failed" + ] + }, + "cleanable": { + "type": "integer" + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "422": { + "description": "No cleanable findings matched", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthScanStartResponse" + } + } + } + } + } + } + }, + "/v1/configs/health:ignore": { + "post": { + "operationId": "setHealthFindingsIgnored", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "setHealthFindingsIgnored", + "description": "Acknowledge 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 at any time — it never disappears for good. Synchronous:\nonly flips a flag on already-computed rows, no rescan needed.\n`ignored` defaults to `true`.\n", + "tags": [ + "Health" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "finding_ids" + ], + "properties": { + "finding_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "ignored": { + "type": "boolean", + "default": true + }, + "reason": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Ignore state updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ok" + ] + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "description": "finding_ids missing or empty", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + } + } + } + }, "/v1/configs/compare": { "post": { "operationId": "compareConfigs", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "compareConfigs", "description": "Compare 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 imported\nfrom caller); configs without a counterpart come back as `only_current`\nor `only_source`.\n\n`source_auth_token` must be a valid token for `source_org_id` — the\nfrontend mints one via the pipeline pairing, mirroring `createSyncJob`'s\n`target_auth_token`. The token is verified against `source_org_id`\nbefore any source-org data is read.\n\nPOST because the request carries a token; the operation reads only.\n", "tags": [ @@ -331,6 +690,11 @@ "/v1/configs/compare/suggestions": { "post": { "operationId": "suggestMatches", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "suggestMatches", "description": "Run 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\nSuggestions are ephemeral — nothing is persisted. The client offers\neach candidate to the user, and a confirmed pair is written via\n`confirmLineage`. Batches are capped at 25 ids; the client pages\nthrough unmatched rows across successive calls.\n", "tags": [ @@ -373,6 +737,11 @@ "/v1/configs/lineage": { "post": { "operationId": "confirmLineage", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], "summary": "confirmLineage", "description": "Persist 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 (caller as\nsync target), so subsequent syncs PATCH the confirmed target instead of\ncreating a duplicate. Conflicts (an existing entry pointing at a\ndifferent target) return 409 so the client can refresh.\n", "tags": [ @@ -423,6 +792,11 @@ }, "delete": { "operationId": "breakLineage", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], "summary": "breakLineage", "description": "Delete 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 re-match the\nsame pair heuristically — the entry is deleted, not blocklisted. To\nbreak a match recorded in the OTHER direction, call this operation\nauthenticated as the other org (pipeline token).\n", "tags": [ @@ -475,6 +849,11 @@ "/v1/configs/sync-jobs": { "post": { "operationId": "createSyncJob", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], "summary": "createSyncJob", "description": "Create 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\nSee `docs/sync/INTERFACES.md` for the locked request/response contract.\n", "tags": [ @@ -515,6 +894,11 @@ }, "get": { "operationId": "listSyncJobs", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "listSyncJobs", "description": "List sync jobs scoped to the caller's organization, paginated with an opaque\ncursor. Defaults to most-recent first.\n", "tags": [ @@ -553,6 +937,11 @@ "/v1/configs/sync-jobs/{id}": { "get": { "operationId": "getSyncJob", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], "summary": "getSyncJob", "description": "Fetch 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", "tags": [ @@ -587,36 +976,32 @@ } } }, - "/v1/configs/sync-jobs/{id}/retry": { - "post": { - "operationId": "retrySyncJob", - "summary": "retrySyncJob", - "description": "Retry 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, source_id)` set and\nenqueues it for execution. Optionally accepts inline payload overrides.\n", + "/v1/configs/sync-jobs/{id}/health": { + "get": { + "operationId": "getSyncJobHealth", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getSyncJobHealth", + "description": "Targeted 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` or\n`patched` and a `target_id` — using the same finding logic as\n`getHealthReport`, plus `source_org_reference` for written payloads\nthat still carry a source-org id, and `secret_value_missing` (warning)\nfor a referenced SecretString variable the job created: a sync copies\na variable's key, never its value, so setting it is a manual step. It runs for `succeeded` and\n`partial` jobs, and for `failed` jobs that wrote at least one config;\notherwise it is `skipped`. Best-effort: it never changes the job's own\nstatus, and its results never replace the org-wide health report.\nReadable from both the source and the target org.\n\nA finished job that never had a check (it finished before checks\nexisted) reports `not_run`. A check left `pending` or `running` for\nlonger than the scan can take is reported as `failed`; both can be\nstarted again with `runSyncJobHealth`.\n", "tags": [ - "Sync" + "Sync", + "Health" ], "parameters": [ { "$ref": "#/components/parameters/SyncJobId" } ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SyncJobRetryRequest" - } - } - } - }, "responses": { - "201": { - "description": "Retry job accepted", + "200": { + "description": "Health check of the job's written configs", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SyncJob" + "$ref": "#/components/schemas/SyncJobHealth" } } } @@ -634,13 +1019,19 @@ } } }, - "/v1/configs/sync-jobs/{id}/cancel": { + "/v1/configs/sync-jobs/{id}/health:run": { "post": { - "operationId": "cancelSyncJob", - "summary": "cancelSyncJob", - "description": "Cancel 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 abort rather than\noverwrite a cancellation with their own outcome.\n\nResources already written to the target are NOT rolled back; cancelling\nstops further work. Jobs already in a terminal state are rejected with\n409.\n", + "operationId": "runSyncJobHealth", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "runSyncJobHealth", + "description": "Start 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. Same scope\nand rules as the automatic check after every import. Callable from the\nsource or the target org; the scan runs in the target org. Answers with\nthe check's new state (`pending`) and runs asynchronously; poll\n`getSyncJobHealth`.\n", "tags": [ - "Sync" + "Sync", + "Health" ], "parameters": [ { @@ -648,12 +1039,12 @@ } ], "responses": { - "200": { - "description": "Job cancelled", + "202": { + "description": "Health check started", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SyncJob" + "$ref": "#/components/schemas/SyncJobHealth" } } } @@ -669,7 +1060,7 @@ } }, "409": { - "description": "Job already finished", + "description": "`job_not_applied` (a plan: nothing was written), `job_not_finished`\n(the job is still running), or `health_check_running` (a check is\nalready pending or running)\n", "content": { "application/json": { "schema": { @@ -681,40 +1072,51 @@ } } }, - "/v1/configs/sync-jobs/{id}/resources": { - "get": { - "operationId": "listSyncJobResources", - "summary": "listSyncJobResources", - "description": "List the per-resource rows for a sync job. Supports filtering by status\n(e.g. `failed`) and cursor pagination. Used by the failures table and the\ndry-run plan view in the frontend.\n", + "/v1/configs/sync-jobs/{id}/retry": { + "post": { + "operationId": "retrySyncJob", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "retrySyncJob", + "description": "Retry 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, source_id)` set and\nenqueues it for execution. Optionally accepts inline payload overrides.\n\nWith `resources`, only those rows are retried, plus the unresolved rows\nof the original job they depend on (transitively). The new job carries\n`retry_of`; the original job's `retried_by` gains the new job's id.\n", "tags": [ "Sync" ], "parameters": [ { "$ref": "#/components/parameters/SyncJobId" - }, - { - "$ref": "#/components/parameters/CursorParam" - }, - { - "$ref": "#/components/parameters/SizeParam" - }, - { - "name": "status", - "in": "query", - "description": "Filter resources by status", - "schema": { - "$ref": "#/components/schemas/SyncJobResourceStatus" - } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncJobRetryRequest" + } + } + } + }, "responses": { - "200": { - "description": "Paginated sync job resources", + "201": { + "description": "Retry job accepted", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SyncJobResourceListResponse" + "$ref": "#/components/schemas/SyncJob" + } + } + } + }, + "400": { + "description": "Missing `target_auth_token`, or `resources` lists rows that are not\nunresolved rows of the job (listed in `invalid_resources`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncJobRetryErrorResponse" } } } @@ -732,37 +1134,57 @@ } } }, - "/v1/configs/delete-jobs": { + "/v1/configs/sync-jobs/{id}/retry-plan": { "post": { - "operationId": "createDeleteJob", - "summary": "createDeleteJob", - "description": "Create 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\nRaw delete — no dependency checks are performed. On completion the\nworker auto-triggers an index rebuild so the config list reflects the\ndeletions.\n", + "operationId": "planSyncJobRetry", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "planSyncJobRetry", + "description": "Preview 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", "tags": [ - "Delete" + "Sync" + ], + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DeleteJobRequest" + "$ref": "#/components/schemas/SyncJobRetryPlanRequest" } } } }, "responses": { - "201": { - "description": "Delete job accepted", + "200": { + "description": "Retry plan", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DeleteJob" + "$ref": "#/components/schemas/SyncJobRetryPlan" } } } }, "400": { - "description": "Invalid request body", + "description": "`resources` lists rows that are not unresolved rows of the job", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncJobRetryErrorResponse" + } + } + } + }, + "404": { + "description": "Sync job not found", "content": { "application/json": { "schema": { @@ -772,29 +1194,53 @@ } } } - }, - "get": { - "operationId": "listDeleteJobs", - "summary": "listDeleteJobs", - "description": "List bulk-delete jobs scoped to the caller's organization, paginated\nwith an opaque cursor. Most-recent first.\n", + } + }, + "/v1/configs/sync-jobs/{id}/cancel": { + "post": { + "operationId": "cancelSyncJob", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "cancelSyncJob", + "description": "Cancel 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 abort rather than\noverwrite a cancellation with their own outcome.\n\nResources already written to the target are NOT rolled back; cancelling\nstops further work. Jobs already in a terminal state are rejected with\n409.\n", "tags": [ - "Delete" + "Sync" ], "parameters": [ { - "$ref": "#/components/parameters/CursorParam" - }, - { - "$ref": "#/components/parameters/SizeParam" + "$ref": "#/components/parameters/SyncJobId" } ], "responses": { "200": { - "description": "Paginated delete jobs", + "description": "Job cancelled", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DeleteJobListResponse" + "$ref": "#/components/schemas/SyncJob" + } + } + } + }, + "404": { + "description": "Sync job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Job already finished", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -802,32 +1248,68 @@ } } }, - "/v1/configs/delete-jobs/{id}": { + "/v1/configs/sync-jobs/{id}/events": { "get": { - "operationId": "getDeleteJob", - "summary": "getDeleteJob", - "description": "Fetch a single bulk-delete job by ID. Returns the job header and counts\nsummary. Frontend polls this endpoint while the job runs.\n", + "operationId": "listSyncJobEvents", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "listSyncJobEvents", + "description": "Full 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\nJobs that predate the event log are served from the op-log instead\n(`source: op_log`): one event per resource holding its last state.\n`after` is ignored there and the first page is returned.\n", "tags": [ - "Delete" + "Sync" ], "parameters": [ { - "$ref": "#/components/parameters/DeleteJobId" + "$ref": "#/components/parameters/SyncJobId" + }, + { + "$ref": "#/components/parameters/CursorParam" + }, + { + "name": "size", + "in": "query", + "description": "Number of events per page", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 200 + } + }, + { + "name": "after", + "in": "query", + "description": "Only return events strictly newer than this event id (max 500,\nlatest first). `truncated: true` means more new events existed than\nwere returned — reload from the first page.\n", + "schema": { + "type": "string" + } + }, + { + "name": "kind", + "in": "query", + "description": "Comma-separated event kinds to return (`resource`, `dependency`,\n`step`), e.g. `dependency,step`. Omit for every kind. Applies to\npages, `after` polling and `total`.\n", + "schema": { + "type": "string", + "example": "dependency,step" + } } ], "responses": { "200": { - "description": "Delete job detail", + "description": "Page of sync job events", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DeleteJob" + "$ref": "#/components/schemas/SyncJobEventListResponse" } } } }, "404": { - "description": "Delete job not found", + "description": "Sync job not found", "content": { "application/json": { "schema": { @@ -839,38 +1321,75 @@ } } }, - "/v1/configs/delete-jobs/{id}/resources": { + "/v1/configs/sync-jobs/{id}/resources": { "get": { - "operationId": "listDeleteJobResources", - "summary": "listDeleteJobResources", - "description": "List the per-resource rows for a delete job, cursor-paginated. Used by\nthe failures view in the frontend.\n", + "operationId": "listSyncJobResources", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "listSyncJobResources", + "description": "List 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 the frontend.\n\nRows without a stored `title` get one from the config index (the target\norg's copy, else the source org's) when it can be resolved. Rows carry\n`source_link` / `target_link` portal editor URLs where they can be built.\n", "tags": [ - "Delete" + "Sync" ], "parameters": [ { - "$ref": "#/components/parameters/DeleteJobId" + "$ref": "#/components/parameters/SyncJobId" }, { "$ref": "#/components/parameters/CursorParam" }, { - "$ref": "#/components/parameters/SizeParam" + "name": "size", + "in": "query", + "description": "Number of items per page", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000 + } + }, + { + "name": "status", + "in": "query", + "description": "Filter resources by status", + "schema": { + "$ref": "#/components/schemas/SyncJobResourceStatus" + } + }, + { + "name": "type", + "in": "query", + "description": "Filter resources by resource type", + "schema": { + "type": "string" + } + }, + { + "name": "plan_action", + "in": "query", + "description": "Filter resources by plan action (`plan.action`). Only rows of plan\njobs and of jobs applying a plan carry one.\n", + "schema": { + "$ref": "#/components/schemas/SyncPlanAction" + } } ], "responses": { "200": { - "description": "Paginated delete job resources", + "description": "Paginated sync job resources", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DeleteJobResourceListResponse" + "$ref": "#/components/schemas/SyncJobResourceListResponse" } } } }, "404": { - "description": "Delete job not found", + "description": "Sync job not found", "content": { "application/json": { "schema": { @@ -882,21 +1401,51 @@ } } }, - "/v1/configs/index:rebuild": { - "post": { - "operationId": "rebuildIndex", - "summary": "rebuildIndex", - "description": "Rebuild 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", + "/v1/configs/sync-jobs/{id}/plan": { + "get": { + "operationId": "getSyncPlan", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getSyncPlan", + "description": "Summary 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\nEvery resource row of the plan also carries a `plan` object (see\n`listSyncJobResources`, which can filter by `plan_action`). The field\ndiffs behind each update are served by `listSyncPlanDiffs` and\n`getSyncPlanDiff`.\n\nA plan describes the target org at planning time. Apply it with\n`applySyncPlan`, which can leave out resources first.\n", "tags": [ - "Configs" + "Sync" + ], + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } ], "responses": { "200": { - "description": "Index build status", + "description": "Plan summary", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IndexRebuildResponse" + "$ref": "#/components/schemas/SyncPlan" + } + } + } + }, + "404": { + "description": "Sync job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "The job is not a plan (it was not created with `dry_run`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -904,933 +1453,2521 @@ } } }, - "/v1/configs/inventory": { + "/v1/configs/sync-jobs/{id}/plan/diffs": { "get": { - "operationId": "getConfigInventory", - "summary": "getConfigInventory", - "description": "Returns 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 snapshot-api to consume when taking a full-org snapshot.\n", + "operationId": "listSyncPlanDiffs", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "listSyncPlanDiffs", + "description": "Field-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\nOnly resources whose plan action is `create`, `update` or `no_change`\nhave a diff. Pages are ordered by type, then source id.\n", "tags": [ - "Configs" + "Sync" ], "parameters": [ { - "name": "include_dependency_only", + "$ref": "#/components/parameters/SyncJobId" + }, + { + "$ref": "#/components/parameters/CursorParam" + }, + { + "name": "size", "in": "query", - "description": "When true (default), include dependency-only types (e.g. `entity_mapping`)\nin the inventory in addition to the top-level registered types.\n", + "description": "Number of diffs per page", "schema": { - "type": "boolean", - "default": true + "type": "integer", + "default": 20, + "minimum": 1, + "maximum": 50 + } + }, + { + "name": "type", + "in": "query", + "description": "Only diffs of this resource type", + "schema": { + "type": "string" + } + }, + { + "name": "action", + "in": "query", + "description": "Only diffs with this plan action", + "schema": { + "$ref": "#/components/schemas/SyncPlanAction" } } ], "responses": { "200": { - "description": "Fresh inventory of all org configuration resources", + "description": "Page of plan diffs", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ConfigInventoryResponse" + "$ref": "#/components/schemas/SyncPlanDiffListResponse" + } + } + } + }, + "404": { + "description": "Sync job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "The job is not a plan", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } } } } - } - }, - "components": { - "securitySchemes": { - "EpilotAuth": { - "type": "http", - "scheme": "bearer", - "description": "epilot JWT auth token" + }, + "/v1/configs/sync-jobs/{id}/plan/diffs/{type}/{source_id}": { + "get": { + "operationId": "getSyncPlanDiff", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getSyncPlanDiff", + "description": "Field-level diff of one planned resource. See `listSyncPlanDiffs`.\n", + "tags": [ + "Sync" + ], + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + }, + { + "name": "type", + "in": "path", + "required": true, + "description": "Resource type", + "schema": { + "type": "string" + } + }, + { + "name": "source_id", + "in": "path", + "required": true, + "description": "Source-org resource id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Plan diff", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncPlanDiff" + } + } + } + }, + "404": { + "description": "Sync job not found, or the resource has no diff (it is not part of\nthe plan, its plan action is `skip`, planning failed for it, or the\ndiff expired — diffs are kept for 30 days)\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "The job is not a plan", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } } }, - "parameters": { - "ConfigType": { - "name": "type", - "in": "path", - "required": true, - "description": "Configuration resource type", - "schema": { - "$ref": "#/components/schemas/ResourceType" + "/v1/configs/sync-jobs/{id}/plan/apply": { + "post": { + "operationId": "applySyncPlan", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "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", + "tags": [ + "Sync" + ], + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncPlanApplyRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Apply job accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncJob" + } + } + } + }, + "400": { + "description": "Missing `target_auth_token`, an empty selection, or `resources` /\n`exclude` lists rows that are not part of the plan (listed in\n`invalid_resources`)\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncPlanApplyErrorResponse" + } + } + } + }, + "403": { + "description": "The caller is not the plan's source org", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Sync job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "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", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncPlanApplyErrorResponse" + } + } + } + } + } + } + }, + "/v1/configs/sync-jobs/{id}/plan/replan": { + "post": { + "operationId": "replanSyncPlan", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "replanSyncPlan", + "description": "Plan 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 dependencies are expanded again (a newly referenced resource\nis added to the plan), and their targets are read and diffed again.\nAll other rows keep their plan and diff.\n\nWithout `resources` every row is planned again; that also renews the\nplan's 24-hour window (`expires_at`). A partial replan does not,\nbecause the other rows were planned earlier.\n\nThe plan job goes back to `pending` / `in_progress` and is `ready`\nagain when it finishes (`replanned_at` / `planned_at` updated). Poll\n`getSyncPlan`.\n", + "tags": [ + "Sync" + ], + "parameters": [ + { + "$ref": "#/components/parameters/SyncJobId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncPlanReplanRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Replan started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncJob" + } + } + } + }, + "400": { + "description": "Missing `target_auth_token`, empty `resources`, or `resources`\nlists rows that are not part of the plan (`invalid_resources`)\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncPlanApplyErrorResponse" + } + } + } + }, + "403": { + "description": "The caller is not the plan's source org", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Sync job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "The job is not a plan, or it is still running (`plan_not_ready`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncPlanApplyErrorResponse" + } + } + } + } + } + } + }, + "/v1/configs/delete-jobs": { + "post": { + "operationId": "createDeleteJob", + "x-epilot-permissions": [ + { + "action": "configuration_hub:delete" + } + ], + "summary": "createDeleteJob", + "description": "Create 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\nRaw delete — no dependency checks are performed. On completion the\nworker auto-triggers an index rebuild so the config list reflects the\ndeletions.\n", + "tags": [ + "Delete" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteJobRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Delete job accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteJob" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "get": { + "operationId": "listDeleteJobs", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "listDeleteJobs", + "description": "List bulk-delete jobs scoped to the caller's organization, paginated\nwith an opaque cursor. Most-recent first.\n", + "tags": [ + "Delete" + ], + "parameters": [ + { + "$ref": "#/components/parameters/CursorParam" + }, + { + "$ref": "#/components/parameters/SizeParam" + } + ], + "responses": { + "200": { + "description": "Paginated delete jobs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteJobListResponse" + } + } + } + } + } + } + }, + "/v1/configs/delete-jobs/{id}": { + "get": { + "operationId": "getDeleteJob", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getDeleteJob", + "description": "Fetch a single bulk-delete job by ID. Returns the job header and counts\nsummary. Frontend polls this endpoint while the job runs.\n", + "tags": [ + "Delete" + ], + "parameters": [ + { + "$ref": "#/components/parameters/DeleteJobId" + } + ], + "responses": { + "200": { + "description": "Delete job detail", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteJob" + } + } + } + }, + "404": { + "description": "Delete job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/v1/configs/delete-jobs/{id}/resources": { + "get": { + "operationId": "listDeleteJobResources", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "listDeleteJobResources", + "description": "List the per-resource rows for a delete job, cursor-paginated. Used by\nthe failures view in the frontend.\n", + "tags": [ + "Delete" + ], + "parameters": [ + { + "$ref": "#/components/parameters/DeleteJobId" + }, + { + "$ref": "#/components/parameters/CursorParam" + }, + { + "$ref": "#/components/parameters/SizeParam" + } + ], + "responses": { + "200": { + "description": "Paginated delete job resources", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteJobResourceListResponse" + } + } + } + }, + "404": { + "description": "Delete job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/v1/configs/index:rebuild": { + "post": { + "operationId": "rebuildIndex", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "rebuildIndex", + "description": "Rebuild 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", + "tags": [ + "Configs" + ], + "responses": { + "200": { + "description": "Index build status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IndexRebuildResponse" + } + } + } + } + } + } + }, + "/v1/configs/inventory": { + "get": { + "operationId": "getConfigInventory", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getConfigInventory", + "description": "Returns 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 snapshot-api to consume when taking a full-org snapshot.\n", + "tags": [ + "Configs" + ], + "parameters": [ + { + "name": "include_dependency_only", + "in": "query", + "description": "When true (default), include dependency-only types (e.g. `entity_mapping`)\nin the inventory in addition to the top-level registered types.\n", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Fresh inventory of all org configuration resources", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigInventoryResponse" + } + } + } + } + } + } + }, + "/v1/previews": { + "post": { + "operationId": "createPreview", + "x-epilot-permissions": [ + { + "action": "configuration_hub:sync" + } + ], + "summary": "createPreview", + "description": "Starts 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 copy of all of\nthis org's configuration through a sync job. When the copy is done, the\nrequesting user is invited to the preview org as owner with a\n\"your preview org is ready\" email. Runs asynchronously; poll `getPreview`.\n", + "tags": [ + "Previews" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatePreviewRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Preview creation started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Preview" + } + } + } + }, + "409": { + "description": "Too many previews are being created for this org at the same time" + } + } + }, + "get": { + "operationId": "listPreviews", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "listPreviews", + "description": "Lists the previews requested from the caller's current org, newest first (max 50).", + "tags": [ + "Previews" + ], + "responses": { + "200": { + "description": "Previews of this org", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Preview" + } + } + }, + "required": [ + "results" + ] + } + } + } + } + } + } + }, + "/v1/previews/{id}": { + "get": { + "operationId": "getPreview", + "x-epilot-permissions": [ + { + "action": "configuration_hub:view" + } + ], + "summary": "getPreview", + "description": "Returns a preview requested from the caller's current org, with its progress.", + "tags": [ + "Previews" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The preview", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Preview" + } + } + } + }, + "404": { + "description": "Preview not found in this org" + } + } + } + } + }, + "components": { + "securitySchemes": { + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "description": "epilot JWT auth token" + } + }, + "parameters": { + "ConfigType": { + "name": "type", + "in": "path", + "required": true, + "description": "Configuration resource type", + "schema": { + "$ref": "#/components/schemas/ResourceType" + } + }, + "ConfigId": { + "name": "id", + "in": "path", + "required": true, + "description": "Configuration resource ID", + "schema": { + "type": "string" + } + }, + "CursorParam": { + "name": "cursor", + "in": "query", + "description": "Opaque cursor for fetching the next page. Omit for the first page.", + "schema": { + "type": "string" + } + }, + "SizeParam": { + "name": "size", + "in": "query", + "description": "Number of items per page", + "schema": { + "type": "integer", + "default": 25, + "minimum": 1, + "maximum": 100 + } + }, + "SyncJobId": { + "name": "id", + "in": "path", + "required": true, + "description": "Sync job ID", + "schema": { + "type": "string" + } + }, + "DeleteJobId": { + "name": "id", + "in": "path", + "required": true, + "description": "Delete job ID", + "schema": { + "type": "string" + } + } + }, + "schemas": { + "ResourceType": { + "type": "string", + "description": "Configuration resource type identifier.\nMatches blueprint-manifest-api V3 naming conventions.\n", + "enum": [ + "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" + ] + }, + "ConfigTypeInfo": { + "type": "object", + "description": "Static metadata for a config type folder in the tree.\nNo downstream API calls — just type + label + icon + source API info.\n", + "properties": { + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "label": { + "type": "string", + "description": "Display label", + "example": "Journeys" + }, + "icon": { + "type": "string", + "description": "Frontend icon name", + "example": "Route" + }, + "source_api": { + "type": "string", + "description": "Base URL of the epilot API that owns this resource type", + "example": "https://journey.sls.epilot.io" + }, + "sdk_client": { + "type": "string", + "description": "@epilot/sdk subpath for fetching full config payloads", + "example": "@epilot/sdk/journey" + }, + "deletable": { + "type": "boolean", + "description": "Whether resources of this type can be deleted via the bulk-delete API", + "example": true + } + }, + "required": [ + "type", + "label", + "icon", + "source_api", + "sdk_client", + "deletable" + ] + }, + "ConfigNode": { + "type": "object", + "description": "Summary metadata for a single configuration item in the tree", + "properties": { + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "id": { + "type": "string", + "description": "Unique identifier" + }, + "title": { + "type": "string", + "description": "Display name" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Last modified timestamp" + }, + "updated_by": { + "type": "string", + "description": "User who last modified this config" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tags / labels" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Alternative identifiers (short IDs, slugs, variable keys) used in cross-references" + }, + "purposes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Business purposes" + }, + "link": { + "type": "string", + "format": "uri", + "description": "Direct link to open this config in epilot" + }, + "active": { + "type": "boolean", + "description": "Whether this config is currently active/enabled (omitted when not applicable)" + }, + "blueprints": { + "type": "array", + "description": "Installed blueprints that produced this config (tagged during the rebuild lineage pass)", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id", + "title" + ] + } + }, + "metadata": { + "type": "object", + "description": "Type-specific metadata (e.g., submission count for journeys)", + "additionalProperties": true + } + }, + "required": [ + "type", + "id", + "title" + ] + }, + "ConfigListResponse": { + "type": "object", + "description": "Cursor-paginated list of configs for a specific type", + "properties": { + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "label": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "total": { + "type": "integer", + "description": "Total number of configs of this type (if known)" + }, + "next_cursor": { + "type": "string", + "description": "Cursor for fetching the next page. Absent when no more pages." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigNode" + } + } + }, + "required": [ + "type", + "label", + "icon", + "results" + ] + }, + "ConfigDependenciesResponse": { + "type": "object", + "description": "Cursor-paginated list of configs referenced by a given config", + "properties": { + "total": { + "type": "integer", + "description": "Total number of dependencies found (if known)" + }, + "next_cursor": { + "type": "string", + "description": "Cursor for fetching the next page. Absent when no more pages." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigNode" + } + } + }, + "required": [ + "results" + ] + }, + "IndexRebuildResponse": { + "type": "object", + "description": "Result of an index rebuild operation", + "properties": { + "status": { + "type": "string", + "enum": [ + "ready", + "building", + "failed", + "already_building" + ] + }, + "last_built_at": { + "type": "string", + "format": "date-time" + }, + "total_items": { + "type": "integer" + }, + "build_duration_ms": { + "type": "integer" + }, + "failed_types": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "status" + ] + }, + "HealthScanStartResponse": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "scanning", + "failed" + ] + }, + "error": { + "type": "string" + } + } + }, + "HealthFinding": { + "type": "object", + "required": [ + "id", + "code", + "severity", + "source_type", + "source_id", + "source_title", + "message", + "first_seen", + "last_seen" + ], + "properties": { + "id": { + "type": "string", + "description": "Stable finding identity — unchanged across rescans." + }, + "code": { + "type": "string", + "enum": [ + "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" + ] + }, + "severity": { + "type": "string", + "enum": [ + "error", + "warning", + "info" + ] + }, + "source_type": { + "type": "string", + "description": "Config type carrying the problem." + }, + "source_id": { + "type": "string" + }, + "source_title": { + "type": "string" + }, + "message": { + "type": "string", + "description": "English prose. Use message_key when rendering to a user." + }, + "message_key": { + "type": "string", + "description": "Translation key for the same sentence, with message_params as interpolation values. Absent for findings whose text comes from @epilot/config-engine.\n" + }, + "message_params": { + "type": "object", + "additionalProperties": true + }, + "ref_type": { + "type": "string", + "description": "Type of the referenced config (reference findings only)." + }, + "ref_id": { + "type": "string" + }, + "ref_title": { + "type": "string" + }, + "source_link": { + "type": "string", + "description": "Portal deep link to the source config." + }, + "ref_link": { + "type": "string", + "description": "Portal deep link to the referenced config (when it exists)." + }, + "owner_type": { + "type": "string", + "description": "Config that owns the affected one, when the affected config has no\neditor of its own (an entity mapping is edited inside its journey).\n" + }, + "owner_id": { + "type": "string" + }, + "owner_title": { + "type": "string" + }, + "owner_link": { + "type": "string", + "description": "Portal deep link to the owning config — where the fix happens." + }, + "path": { + "type": "string", + "description": "Field path of the referencing field in the source payload." + }, + "cleanable": { + "type": "boolean", + "description": "True when the cleanup endpoint can remove this orphan reference." + }, + "ignored": { + "type": "boolean", + "description": "Acknowledged as intentional by a person — excluded from the headline score and default breakdown until un-ignored.\n" + }, + "ignored_at": { + "type": "string", + "format": "date-time" + }, + "ignored_by": { + "type": "string" + }, + "ignored_reason": { + "type": "string" + }, + "first_seen": { + "type": "string", + "format": "date-time", + "description": "Scan timestamp when this finding first appeared." + }, + "last_seen": { + "type": "string", + "format": "date-time" + } + } + }, + "HealthReportResponse": { + "type": "object", + "required": [ + "status", + "findings" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "missing", + "idle", + "scanning", + "cleaning", + "ready", + "failed" + ] + }, + "last_scanned_at": { + "type": "string", + "format": "date-time" + }, + "scan_duration_ms": { + "type": "integer" + }, + "scanned_configs": { + "type": "integer" + }, + "scanned_by_type": { + "type": "object", + "additionalProperties": { + "type": "integer" + }, + "description": "Configs fetched per resource type in the latest scan." + }, + "counts": { + "type": "object", + "properties": { + "error": { + "type": "integer" + }, + "warning": { + "type": "integer" + }, + "info": { + "type": "integer" + } + } + }, + "new_since_previous": { + "type": "integer", + "description": "Findings whose first_seen is the latest scan." + }, + "resolved_since_previous": { + "type": "integer", + "description": "Findings present in the previous scan but gone now." + }, + "last_cleanup_at": { + "type": "string", + "format": "date-time" + }, + "cleaned_count": { + "type": "integer" + }, + "history": { + "type": "array", + "description": "One entry per completed scan, oldest first — powers the trend.", + "items": { + "type": "object", + "required": [ + "scanned_at", + "clean", + "total" + ], + "properties": { + "scanned_at": { + "type": "string", + "format": "date-time" + }, + "clean": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + } + }, + "error": { + "type": "string" + }, + "findings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HealthFinding" + } + } + } + }, + "IndexStatusResponse": { + "type": "object", + "description": "Current index build state", + "properties": { + "status": { + "type": "string", + "enum": [ + "missing", + "building", + "ready", + "failed" + ] + }, + "org_id": { + "type": "string", + "description": "Organization the index (and the authenticated token) belongs to.\nEchoed so callers holding a token for another org can resolve and\nverify its identity.\n" + }, + "last_built_at": { + "type": "string", + "format": "date-time" + }, + "total_items": { + "type": "integer" + }, + "build_duration_ms": { + "type": "integer" + } + }, + "required": [ + "status" + ] + }, + "CompareRequest": { + "type": "object", + "description": "Request body for a per-type cross-org comparison", + "properties": { + "source_org_id": { + "type": "string", + "description": "Organization to compare the caller's org against" + }, + "source_auth_token": { + "type": "string", + "description": "Auth token belonging to `source_org_id` (e.g. a pipeline token).\nVerified before any source-org data is read. Never logged.\n" + }, + "type": { + "$ref": "#/components/schemas/ResourceType" + } + }, + "required": [ + "source_org_id", + "source_auth_token", + "type" + ] + }, + "CompareLineage": { + "type": "object", + "description": "Provenance of a lineage-backed match", + "properties": { + "direction": { + "type": "string", + "enum": [ + "pull", + "push" + ], + "description": "`pull` — the caller org imported this config from the source org\n(entry lives in the caller's lineage partition). `push` — the\nsource org imported it from the caller (entry lives in the source\norg's partition).\n" + }, + "last_synced_at": { + "type": "string", + "format": "date-time" + }, + "last_sync_job_id": { + "type": "string" + }, + "origin": { + "type": "string", + "description": "`manual_confirm` when the entry was created by confirming a\nheuristic suggestion in the Compare view; absent for sync-written\nentries.\n" + } + }, + "required": [ + "direction" + ] + }, + "CompareRow": { + "type": "object", + "description": "One 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", + "properties": { + "match_status": { + "type": "string", + "enum": [ + "matched", + "only_current", + "only_source" + ] + }, + "current": { + "$ref": "#/components/schemas/ConfigNode" + }, + "source": { + "$ref": "#/components/schemas/ConfigNode" + }, + "lineage": { + "$ref": "#/components/schemas/CompareLineage" + } + }, + "required": [ + "match_status" + ] + }, + "CompareCounts": { + "type": "object", + "description": "Row counts per match status", + "properties": { + "matched": { + "type": "integer" + }, + "only_current": { + "type": "integer" + }, + "only_source": { + "type": "integer" + } + }, + "required": [ + "matched", + "only_current", + "only_source" + ] + }, + "CompareResponse": { + "type": "object", + "description": "Side-by-side comparison of one config type across two orgs", + "properties": { + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "label": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rows": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CompareRow" + } + }, + "counts": { + "$ref": "#/components/schemas/CompareCounts" + } + }, + "required": [ + "type", + "label", + "icon", + "rows", + "counts" + ] + }, + "SuggestMatchesRequest": { + "type": "object", + "description": "Request body for batched heuristic match suggestions", + "properties": { + "source_org_id": { + "type": "string" + }, + "source_auth_token": { + "type": "string", + "description": "Auth token belonging to `source_org_id`. Never logged." + }, + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "source_ids": { + "type": "array", + "description": "Source-org config ids to find caller-org candidates for", + "items": { + "type": "string" + }, + "minItems": 1, + "maxItems": 25 + } + }, + "required": [ + "source_org_id", + "source_auth_token", + "type", + "source_ids" + ] + }, + "MatchSuggestion": { + "type": "object", + "description": "Heuristic candidate for one source config (null candidate = no match)", + "properties": { + "source_id": { + "type": "string" + }, + "candidate": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "required": [ + "source_id" + ] + }, + "SuggestMatchesResponse": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "suggestions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchSuggestion" + } + } + }, + "required": [ + "type", + "suggestions" + ] + }, + "ConfirmLineageRequest": { + "type": "object", + "description": "Persist a source→target lineage pairing (caller org as target)", + "properties": { + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "source_org_id": { + "type": "string" + }, + "source_id": { + "type": "string", + "description": "Config id in the source org" + }, + "target_id": { + "type": "string", + "description": "Config id in the caller's org to pair it with" + } + }, + "required": [ + "type", + "source_org_id", + "source_id", + "target_id" + ] + }, + "LineageEntryResponse": { + "type": "object", + "description": "A persisted lineage entry", + "properties": { + "type": { + "$ref": "#/components/schemas/ResourceType" + }, + "target_org_id": { + "type": "string" + }, + "source_org_id": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "target_id": { + "type": "string" + }, + "last_synced_at": { + "type": "string", + "format": "date-time" + }, + "origin": { + "type": "string" + } + }, + "required": [ + "type", + "target_org_id", + "source_org_id", + "source_id", + "target_id" + ] + }, + "BreakLineageResponse": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean" + } + }, + "required": [ + "deleted" + ] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "code": { + "type": "string", + "description": "Machine-readable reason, where the operation documents one" + }, + "error": { + "type": "string" + } + }, + "required": [ + "status", + "error" + ] + }, + "SyncJobStatus": { + "type": "string", + "description": "Lifecycle status of a sync job. See `docs/sync/INTERFACES.md` for state\ntransitions.\n", + "enum": [ + "pending", + "in_progress", + "succeeded", + "partial", + "failed", + "cancelled" + ] + }, + "SyncDirection": { + "type": "string", + "description": "Direction of the sync, derived from the source/target pane selection in the\nconfiguration hub UI.\n", + "enum": [ + "push", + "pull" + ] + }, + "SyncPhase": { + "type": "string", + "description": "Three-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, `finalize` runs cycle-breaking\nfinalizers. Clients should tolerate phase values they don't know.\n", + "enum": [ + "phase_0", + "dependencies", + "phase_a", + "phase_a5", + "phase_b", + "finalize" + ] + }, + "SyncJobResourceStatus": { + "type": "string", + "description": "Per-resource status. `would_*` values are produced by dry-run jobs.\n", + "enum": [ + "pending", + "in_progress", + "created", + "patched", + "skipped", + "would_create", + "would_patch", + "would_skip_unchanged", + "would_match_heuristic", + "failed" + ] + }, + "SyncJobRequest": { + "type": "object", + "description": "Request body for `createSyncJob`. `target_auth_token` is the destination\norg's auth token and MUST NOT be persisted or logged — it is `writeOnly`.\n", + "required": [ + "source_org_id", + "target_org_id", + "target_auth_token", + "resources" + ], + "properties": { + "source_org_id": { + "type": "string", + "description": "Org ID the resources are sourced from." + }, + "target_org_id": { + "type": "string", + "description": "Org ID the resources are written into." + }, + "target_auth_token": { + "type": "string", + "writeOnly": true, + "description": "Destination-org auth token forwarded to adapter writes. Never returned\nin responses and never logged.\n" + }, + "name": { + "type": "string", + "description": "Optional human-friendly job name shown in the history list." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "If true, the job is a plan: the orchestrator runs Phase 0 + a\nplanning pass, reads the target's copies, writes `would_*`\nresource rows with a `plan` (create / update / no change) and\nfield diffs, and performs no destination writes. See\n`getSyncPlan`, `listSyncPlanDiffs` and `applySyncPlan`.\n" + }, + "include_dependencies": { + "type": "boolean", + "default": true, + "description": "If true, the orchestrator expands the resource set by following\ndependency edges discovered during Phase 0.\n" + }, + "sync_portal_widgets": { + "type": "boolean", + "default": true, + "description": "If true (default), a synced `portal_config` also pushes the source\nportal's widgets to the target portal (a full replace of the target's\nwidget set). Set false to leave the target portal's widgets alone.\n" + }, + "resources": { + "type": "array", + "description": "Initial resource selection. Dependencies may be added.", + "items": { + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "description": "Resource type identifier" + }, + "id": { + "type": "string", + "description": "Source-org resource ID" + } + } + } + } } }, - "ConfigId": { - "name": "id", - "in": "path", - "required": true, - "description": "Configuration resource ID", - "schema": { - "type": "string" - } + "SyncJobResourceRef": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "source_id": { + "type": "string" + } + }, + "required": [ + "type", + "source_id" + ] }, - "CursorParam": { - "name": "cursor", - "in": "query", - "description": "Opaque cursor for fetching the next page. Omit for the first page.", - "schema": { - "type": "string" + "SyncJobRetryPlanRequest": { + "type": "object", + "properties": { + "resources": { + "type": "array", + "description": "Rows of the original job to retry. Each must be `failed`, `pending`\nor `in_progress`. Omit to retry every unresolved row.\n", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + } } }, - "SizeParam": { - "name": "size", - "in": "query", - "description": "Number of items per page", - "schema": { - "type": "integer", - "default": 25, - "minimum": 1, - "maximum": 100 - } + "SyncJobRetryPlan": { + "type": "object", + "properties": { + "selected": { + "type": "array", + "description": "The selected rows of the original job.", + "items": { + "$ref": "#/components/schemas/SyncJobResource" + } + }, + "added_dependencies": { + "type": "array", + "description": "Unresolved rows the selection depends on, added to the retry.", + "items": { + "$ref": "#/components/schemas/SyncJobResource" + } + }, + "dependency_resolution": { + "type": "string", + "description": "`best_effort` when some source payloads could not be fetched, so\ndependencies may be missing from `added_dependencies`.\n", + "enum": [ + "exact", + "best_effort" + ] + } + }, + "required": [ + "selected", + "added_dependencies", + "dependency_resolution" + ] }, - "SyncJobId": { - "name": "id", - "in": "path", - "required": true, - "description": "Sync job ID", - "schema": { - "type": "string" + "SyncJobRetryErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "error": { + "type": "string" + }, + "invalid_resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + } } }, - "DeleteJobId": { - "name": "id", - "in": "path", - "required": true, - "description": "Delete job ID", - "schema": { - "type": "string" + "SyncJobRetryRequest": { + "type": "object", + "description": "Optional body for `retrySyncJob`. Defaults to retrying every unresolved\nresource of the original job (failed, pending or in_progress).\n", + "properties": { + "target_auth_token": { + "type": "string", + "description": "Target org token used by the worker for writes. Required." + }, + "resources": { + "type": "array", + "description": "Retry only these rows (each must be `failed`, `pending` or\n`in_progress`) plus the unresolved rows they depend on.\n", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + }, + "payload_overrides": { + "type": "object", + "description": "Map of `:` → partial payload patch. Applied on top of\nthe originally fetched payload before re-running Phase A.\n", + "additionalProperties": true + } } - } - }, - "schemas": { - "ResourceType": { - "type": "string", - "description": "Configuration resource type identifier.\nMatches blueprint-manifest-api V3 naming conventions.\n", - "enum": [ - "journey", - "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" - ] }, - "ConfigTypeInfo": { + "SyncJobCounts": { "type": "object", - "description": "Static metadata for a config type folder in the tree.\nNo downstream API calls — just type + label + icon + source API info.\n", + "description": "Aggregate counters by resource status.", "properties": { - "type": { - "$ref": "#/components/schemas/ResourceType" + "total": { + "type": "integer" }, - "label": { - "type": "string", - "description": "Display label", - "example": "Journeys" + "pending": { + "type": "integer" }, - "icon": { - "type": "string", - "description": "Frontend icon name", - "example": "Route" + "in_progress": { + "type": "integer" }, - "source_api": { - "type": "string", - "description": "Base URL of the epilot API that owns this resource type", - "example": "https://journey.sls.epilot.io" + "succeeded": { + "type": "integer" }, - "sdk_client": { - "type": "string", - "description": "@epilot/sdk subpath for fetching full config payloads", - "example": "@epilot/sdk/journey" + "failed": { + "type": "integer" }, - "deletable": { - "type": "boolean", - "description": "Whether resources of this type can be deleted via the bulk-delete API", - "example": true + "skipped_unchanged": { + "type": "integer" } }, "required": [ - "type", - "label", - "icon", - "source_api", - "sdk_client", - "deletable" + "total", + "pending", + "in_progress", + "succeeded", + "failed", + "skipped_unchanged" ] }, - "ConfigNode": { + "SyncJobEvent": { "type": "object", - "description": "Summary metadata for a single configuration item in the tree", + "description": "Activity-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", "properties": { - "type": { - "$ref": "#/components/schemas/ResourceType" - }, "id": { "type": "string", - "description": "Unique identifier" + "description": "Event id. A ULID (time-sortable) for event-log events, `op-`\nfor op-log events. Absent on `getSyncJob.events`.\n" }, - "title": { + "kind": { "type": "string", - "description": "Display name" + "description": "What the event describes. `resource`: a status change of a resource\nrow. `dependency`: dependency expansion resolved a referenced\nresource (see `dependency`). `step`: a phase milestone such as the\nsource-index check or the dependency summary; has no `type` or\n`source_id`. Absent means `resource`.\n", + "enum": [ + "resource", + "dependency", + "step" + ] }, - "updated_at": { + "seq": { + "type": "integer", + "description": "Resource sequence number of the op-log write (the resource's index\nin the job). Not unique per event. 0 for `dependency` and `step`\nevents.\n" + }, + "ts": { "type": "string", - "format": "date-time", - "description": "Last modified timestamp" + "format": "date-time" }, - "updated_by": { + "phase": { + "$ref": "#/components/schemas/SyncPhase" + }, + "type": { "type": "string", - "description": "User who last modified this config" + "description": "Resource type" }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Tags / labels" + "source_id": { + "type": "string" }, - "aliases": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Alternative identifiers (short IDs, slugs, variable keys) used in cross-references" + "target_id": { + "type": "string" + }, + "title": { + "type": "string", + "description": "Resource title, when known at write time." + }, + "status": { + "$ref": "#/components/schemas/SyncJobResourceStatus" + }, + "attempt": { + "type": "integer" + }, + "message": { + "type": "string" }, - "purposes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Business purposes" + "error": { + "type": "string" }, - "link": { + "dependency": { + "$ref": "#/components/schemas/SyncJobEventDependency" + } + }, + "required": [ + "seq", + "ts", + "status" + ] + }, + "SyncJobEventDependency": { + "type": "object", + "description": "Only on `kind: dependency` events. The event's `type`, `source_id` and\n`title` describe the dependency itself.\n", + "properties": { + "outcome": { "type": "string", - "format": "uri", - "description": "Direct link to open this config in epilot" + "description": "`added`: not yet in the target, fetched and added to the sync set.\n`linked`: already synced to the target (`target_id`), mapped but not\nsynced again. `skipped`: not syncable (unsupported type, archived or\nbuilt-in). `not_found`: couldn't be loaded from the source org.\n", + "enum": [ + "added", + "linked", + "skipped", + "not_found" + ] }, - "active": { - "type": "boolean", - "description": "Whether this config is currently active/enabled (omitted when not applicable)" + "reason": { + "$ref": "#/components/schemas/SyncDependencyReason" }, - "blueprints": { - "type": "array", - "description": "Installed blueprints that produced this config (tagged during the rebuild lineage pass)", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "required": [ - "id", - "title" - ] - } + "required_by": { + "$ref": "#/components/schemas/SyncRequiredBy" }, - "metadata": { - "type": "object", - "description": "Type-specific metadata (e.g., submission count for journeys)", - "additionalProperties": true + "depth": { + "type": "integer", + "description": "Expansion depth. 0 = referenced directly by a selected resource.\n" } }, "required": [ - "type", - "id", - "title" + "outcome", + "reason", + "required_by", + "depth" ] }, - "ConfigListResponse": { + "SyncDependencyReason": { + "type": "string", + "description": "How 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_datasource`: a datasource owned by the\njourney. `file_reference`: a file used by the resource.\n", + "enum": [ + "typed_reference", + "id_reference", + "automation_trigger", + "journey_datasource", + "file_reference" + ] + }, + "SyncRequiredBy": { "type": "object", - "description": "Cursor-paginated list of configs for a specific type", + "description": "The resource that referenced the dependency.", "properties": { "type": { - "$ref": "#/components/schemas/ResourceType" + "type": "string" }, - "label": { + "source_id": { "type": "string" }, - "icon": { + "title": { + "type": "string" + } + }, + "required": [ + "type", + "source_id" + ] + }, + "SyncJobEventListResponse": { + "type": "object", + "properties": { + "results": { + "type": "array", + "description": "Events, latest first.", + "items": { + "$ref": "#/components/schemas/SyncJobEvent" + } + }, + "next_cursor": { "type": "string" }, "total": { "type": "integer", - "description": "Total number of configs of this type (if known)" + "description": "Number of events matching the `kind` filter. Only on the first page\n(no `cursor`, no `after`).\n" }, - "next_cursor": { - "type": "string", - "description": "Cursor for fetching the next page. Absent when no more pages." + "truncated": { + "type": "boolean", + "description": "Only with `after`: more new events existed than were returned.\nReload from the first page.\n" }, - "results": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ConfigNode" - } + "source": { + "type": "string", + "description": "Which store served the events.", + "enum": [ + "events", + "op_log" + ] } }, "required": [ - "type", - "label", - "icon", - "results" + "results", + "source" ] }, - "ConfigDependenciesResponse": { + "SyncJobBatch": { "type": "object", - "description": "Cursor-paginated list of configs referenced by a given config", + "description": "Position within the current topological batch for the active phase.", "properties": { - "total": { + "index": { "type": "integer", - "description": "Total number of dependencies found (if known)" + "description": "Zero-based index of the batch currently executing." }, - "next_cursor": { - "type": "string", - "description": "Cursor for fetching the next page. Absent when no more pages." + "of": { + "type": "integer", + "description": "Total number of batches in the current phase." }, - "results": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ConfigNode" - } + "level": { + "type": "integer", + "description": "Dependency level (matches `dependencyLevel` from the topological sort)." } }, "required": [ - "results" + "index", + "of", + "level" ] }, - "IndexRebuildResponse": { + "SyncJobHealthStatus": { + "type": "string", + "description": "State 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 finished job that never had a check (start one with\n`runSyncJobHealth`).\n", + "enum": [ + "pending", + "running", + "done", + "failed", + "skipped", + "not_run" + ] + }, + "HealthSeverityCounts": { "type": "object", - "description": "Result of an index rebuild operation", + "required": [ + "error", + "warning", + "info" + ], "properties": { - "status": { - "type": "string", - "enum": [ - "ready", - "building", - "failed", - "already_building" - ] - }, - "last_built_at": { - "type": "string", - "format": "date-time" - }, - "total_items": { + "error": { "type": "integer" }, - "build_duration_ms": { + "warning": { "type": "integer" }, - "failed_types": { - "type": "array", - "items": { - "type": "string" - } + "info": { + "type": "integer" } - }, + } + }, + "SyncJobHealthCheck": { + "type": "object", + "description": "Compact 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", "required": [ - "status" - ] + "status", + "counts" + ], + "properties": { + "status": { + "$ref": "#/components/schemas/SyncJobHealthStatus" + }, + "counts": { + "$ref": "#/components/schemas/HealthSeverityCounts" + } + } }, - "IndexStatusResponse": { + "SyncJobHealth": { "type": "object", - "description": "Current index build state", + "description": "Targeted health check of the configs a sync job wrote into the target\norg. Findings carry `first_seen` = `last_seen` = `finished_at`.\n", + "required": [ + "status", + "scanned", + "counts", + "findings" + ], "properties": { "status": { - "type": "string", - "enum": [ - "missing", - "building", - "ready", - "failed" - ] + "$ref": "#/components/schemas/SyncJobHealthStatus" }, - "org_id": { + "started_at": { "type": "string", - "description": "Organization the index (and the authenticated token) belongs to.\nEchoed so callers holding a token for another org can resolve and\nverify its identity.\n" + "format": "date-time" }, - "last_built_at": { + "finished_at": { "type": "string", "format": "date-time" }, - "total_items": { - "type": "integer" + "scanned": { + "type": "integer", + "description": "Written configs whose payload was read." }, - "build_duration_ms": { - "type": "integer" - } - }, - "required": [ - "status" - ] - }, - "CompareRequest": { - "type": "object", - "description": "Request body for a per-type cross-org comparison", - "properties": { - "source_org_id": { - "type": "string", - "description": "Organization to compare the caller's org against" + "counts": { + "$ref": "#/components/schemas/HealthSeverityCounts" }, - "source_auth_token": { + "error": { "type": "string", - "description": "Auth token belonging to `source_org_id` (e.g. a pipeline token).\nVerified before any source-org data is read. Never logged.\n" + "description": "Why the check failed or was skipped." }, - "type": { - "$ref": "#/components/schemas/ResourceType" + "findings": { + "type": "array", + "description": "Errors first, then warnings, then info. Empty unless `done`.", + "items": { + "$ref": "#/components/schemas/HealthFinding" + } } - }, - "required": [ - "source_org_id", - "source_auth_token", - "type" - ] + } }, - "CompareLineage": { + "SyncJob": { "type": "object", - "description": "Provenance of a lineage-backed match", + "description": "Sync job header as surfaced by `getSyncJob` and the create response. The\ncanonical persistence shape is described in `docs/sync/INTERFACES.md`.\n", "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/SyncJobStatus" + }, "direction": { + "$ref": "#/components/schemas/SyncDirection" + }, + "source_org_id": { + "type": "string" + }, + "target_org_id": { + "type": "string" + }, + "dry_run": { + "type": "boolean" + }, + "counts": { + "$ref": "#/components/schemas/SyncJobCounts" + }, + "current_phase": { + "$ref": "#/components/schemas/SyncPhase" + }, + "current_batch": { + "$ref": "#/components/schemas/SyncJobBatch" + }, + "started_at": { "type": "string", - "enum": [ - "pull", - "push" - ], - "description": "`pull` — the caller org imported this config from the source org\n(entry lives in the caller's lineage partition). `push` — the\nsource org imported it from the caller (entry lives in the source\norg's partition).\n" + "format": "date-time" }, - "last_synced_at": { + "finished_at": { "type": "string", "format": "date-time" }, - "last_sync_job_id": { - "type": "string" + "health_check": { + "$ref": "#/components/schemas/SyncJobHealthCheck" }, - "origin": { - "type": "string", - "description": "`manual_confirm` when the entry was created by confirming a\nheuristic suggestion in the Compare view; absent for sync-written\nentries.\n" - } - }, - "required": [ - "direction" - ] - }, - "CompareRow": { - "type": "object", - "description": "One 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", - "properties": { - "match_status": { + "events": { + "type": "array", + "description": "Deprecated — use `listSyncJobEvents`. Up to 10 op-log events,\nordered by resource index rather than recency.\n", + "deprecated": true, + "items": { + "$ref": "#/components/schemas/SyncJobEvent" + } + }, + "created_by": { "type": "string", - "enum": [ - "matched", - "only_current", - "only_source" - ] + "description": "Id of the user who started the job." }, - "current": { - "$ref": "#/components/schemas/ConfigNode" + "retry_of": { + "type": "string", + "description": "Id of the job this job retries." }, - "source": { - "$ref": "#/components/schemas/ConfigNode" + "retried_by": { + "type": "array", + "description": "Ids of the retry jobs created from this job, oldest first.", + "items": { + "type": "string" + } }, - "lineage": { - "$ref": "#/components/schemas/CompareLineage" - } - }, - "required": [ - "match_status" - ] - }, - "CompareCounts": { - "type": "object", - "description": "Row counts per match status", - "properties": { - "matched": { - "type": "integer" + "plan_only": { + "type": "boolean", + "description": "True on a finished plan (`dry_run`) job." }, - "only_current": { - "type": "integer" + "plan_summary": { + "$ref": "#/components/schemas/SyncPlanCounts" }, - "only_source": { - "type": "integer" - } - }, - "required": [ - "matched", - "only_current", - "only_source" - ] - }, - "CompareResponse": { - "type": "object", - "description": "Side-by-side comparison of one config type across two orgs", - "properties": { - "type": { - "$ref": "#/components/schemas/ResourceType" + "planned_at": { + "type": "string", + "format": "date-time", + "description": "On a plan, when every row was last planned (first run or full replan)." }, - "label": { - "type": "string" + "expires_at": { + "type": "string", + "format": "date-time", + "description": "On a finished plan, when it stops being applicable (24 hours after\n`planned_at`). Same value as `getSyncPlan`'s `expires_at`.\n" }, - "icon": { - "type": "string" + "applied_from_plan": { + "type": "string", + "description": "Id of the plan this job applies (see `applySyncPlan`)." }, - "rows": { + "applied_by": { "type": "array", + "description": "Ids of the jobs that applied this plan, oldest first.", "items": { - "$ref": "#/components/schemas/CompareRow" + "type": "string" } }, - "counts": { - "$ref": "#/components/schemas/CompareCounts" - } - }, - "required": [ - "type", - "label", - "icon", - "rows", - "counts" - ] - }, - "SuggestMatchesRequest": { - "type": "object", - "description": "Request body for batched heuristic match suggestions", - "properties": { - "source_org_id": { - "type": "string" + "approved_by": { + "type": "string", + "description": "On a job applying a plan, the user who approved the plan." }, - "source_auth_token": { + "approved_at": { "type": "string", - "description": "Auth token belonging to `source_org_id`. Never logged." + "format": "date-time", + "description": "On a job applying a plan, when the plan was approved." }, - "type": { - "$ref": "#/components/schemas/ResourceType" + "approvals": { + "type": "array", + "description": "On a plan, every approval (one per `applySyncPlan` call), oldest\nfirst: the apply job, who approved and when.\n", + "items": { + "type": "object", + "properties": { + "job_id": { + "type": "string" + }, + "approved_by": { + "type": "string" + }, + "approved_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "job_id", + "approved_at" + ] + } }, - "source_ids": { + "errors_sample": { "type": "array", - "description": "Source-org config ids to find caller-org candidates for", + "description": "Up to 20 sample error messages from failed resources.", + "maxItems": 20, "items": { - "type": "string" - }, - "minItems": 1, - "maxItems": 25 + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "error": { + "type": "string" + } + }, + "required": [ + "type", + "source_id", + "error" + ] + } } }, "required": [ + "id", + "status", + "direction", "source_org_id", - "source_auth_token", - "type", - "source_ids" + "target_org_id", + "dry_run", + "counts", + "started_at" ] }, - "MatchSuggestion": { + "SyncJobListResponse": { "type": "object", - "description": "Heuristic candidate for one source config (null candidate = no match)", + "description": "Cursor-paginated list of sync jobs.", "properties": { - "source_id": { + "next_cursor": { "type": "string" }, - "candidate": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "required": [ - "id" - ] - } - }, - "required": [ - "source_id" - ] - }, - "SuggestMatchesResponse": { - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/ResourceType" - }, - "suggestions": { + "results": { "type": "array", "items": { - "$ref": "#/components/schemas/MatchSuggestion" + "$ref": "#/components/schemas/SyncJob" } } }, "required": [ - "type", - "suggestions" + "results" ] }, - "ConfirmLineageRequest": { + "SyncJobResource": { "type": "object", - "description": "Persist a source→target lineage pairing (caller org as target)", + "description": "Per-resource row backed by `SyncResourcesTable`. See\n`docs/sync/INTERFACES.md` for the DDB shape.\n", "properties": { "type": { - "$ref": "#/components/schemas/ResourceType" - }, - "source_org_id": { "type": "string" }, "source_id": { - "type": "string", - "description": "Config id in the source org" + "type": "string" }, "target_id": { + "type": "string" + }, + "title": { "type": "string", - "description": "Config id in the caller's org to pair it with" - } - }, - "required": [ - "type", - "source_org_id", - "source_id", - "target_id" - ] - }, - "LineageEntryResponse": { - "type": "object", - "description": "A persisted lineage entry", - "properties": { - "type": { - "$ref": "#/components/schemas/ResourceType" + "description": "Human-readable resource title captured from the source payload\nduring Phase 0. Lets clients identify rows without resolving\nraw UUIDs. Absent on rows written before this field existed.\n" }, - "target_org_id": { - "type": "string" + "required_by": { + "type": "object", + "description": "Set when dependency expansion added the row to the sync set: the\nresource that referenced it and why. Absent on rows the user\nselected and on rows written before this field existed.\n", + "properties": { + "type": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "reason": { + "$ref": "#/components/schemas/SyncDependencyReason" + } + }, + "required": [ + "type", + "source_id", + "reason" + ] + }, + "status": { + "$ref": "#/components/schemas/SyncJobResourceStatus" }, - "source_org_id": { - "type": "string" + "phase": { + "$ref": "#/components/schemas/SyncPhase" }, - "source_id": { - "type": "string" + "attempt": { + "type": "integer" }, - "target_id": { + "error": { "type": "string" }, - "last_synced_at": { + "updated_at": { "type": "string", "format": "date-time" }, - "origin": { - "type": "string" + "source_link": { + "type": "string", + "description": "Portal editor URL of the resource in the source org. Absent when it\ncan't be built reliably (e.g. entity-scoped types without schema\ncontext).\n" + }, + "target_link": { + "type": "string", + "description": "Portal editor URL of the resource in the target org. Only present\nwhen the row has a `target_id`; absent when it can't be built\nreliably.\n" + }, + "plan": { + "$ref": "#/components/schemas/SyncResourcePlan" + }, + "excluded_from_apply": { + "type": "string", + "description": "Only on jobs that apply a plan: why the row was not applied.\n`unchanged` (the plan found no change), `excluded` (left out by\nthe request) or `not_syncable` (the plan would skip it). Such rows\nare `skipped`.\n", + "enum": [ + "unchanged", + "excluded", + "not_syncable" + ] } }, "required": [ "type", - "target_org_id", - "source_org_id", "source_id", - "target_id" - ] - }, - "BreakLineageResponse": { - "type": "object", - "properties": { - "deleted": { - "type": "boolean" - } - }, - "required": [ - "deleted" + "status", + "phase", + "attempt", + "updated_at" ] }, - "ErrorResponse": { + "SyncJobResourceListResponse": { "type": "object", + "description": "Cursor-paginated list of sync job resources.", "properties": { - "status": { - "type": "integer" - }, - "error": { + "next_cursor": { "type": "string" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SyncJobResource" + } } }, "required": [ - "status", - "error" - ] - }, - "SyncJobStatus": { - "type": "string", - "description": "Lifecycle status of a sync job. See `docs/sync/INTERFACES.md` for state\ntransitions.\n", - "enum": [ - "pending", - "in_progress", - "succeeded", - "partial", - "failed", - "cancelled" + "results" ] }, - "SyncDirection": { + "SyncPlanAction": { "type": "string", - "description": "Direction of the sync, derived from the source/target pane selection in the\nconfiguration hub UI.\n", + "description": "What 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-applied to be\n safe.\n- `no_change`: the target's copy already matches the source.\n- `skip`: the resource is never synced (e.g. built-in taxonomies,\n credentials).\n", "enum": [ - "push", - "pull" + "create", + "update", + "no_change", + "skip" ] }, - "SyncPhase": { + "SyncPlanMatch": { "type": "string", - "description": "Three-phase orchestrator phase. `phase_0` fetches source payloads,\n`phase_a` creates/matches with topological batches, `phase_a5` resolves\nderived references, `phase_b` patches with the full ID map, `finalize`\nruns cycle-breaking finalizers.\n", + "description": "How 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", "enum": [ - "phase_0", - "phase_a", - "phase_a5", - "phase_b", - "finalize" + "lineage", + "heuristic", + "none" ] }, - "SyncJobResourceStatus": { + "SyncPlanDiffStatus": { "type": "string", - "description": "Per-resource status. `would_*` values are produced by dry-run jobs.\n", + "description": "`computed` when both copies were read and compared. `not_compared`\nwhen the target could not be read or compared (see `note`).\n", "enum": [ - "pending", - "in_progress", - "created", - "patched", - "skipped", - "would_create", - "would_patch", - "would_skip_unchanged", - "would_match_heuristic", - "failed" + "computed", + "not_compared" ] }, - "SyncJobRequest": { + "SyncResourcePlan": { "type": "object", - "description": "Request body for `createSyncJob`. `target_auth_token` is the destination\norg's auth token and MUST NOT be persisted or logged — it is `writeOnly`.\n", - "required": [ - "source_org_id", - "target_org_id", - "target_auth_token", - "resources" - ], + "description": "The 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", "properties": { - "source_org_id": { - "type": "string", - "description": "Org ID the resources are sourced from." + "action": { + "$ref": "#/components/schemas/SyncPlanAction" }, - "target_org_id": { - "type": "string", - "description": "Org ID the resources are written into." + "match": { + "$ref": "#/components/schemas/SyncPlanMatch" }, - "target_auth_token": { - "type": "string", - "writeOnly": true, - "description": "Destination-org auth token forwarded to adapter writes. Never returned\nin responses and never logged.\n" + "diff_status": { + "$ref": "#/components/schemas/SyncPlanDiffStatus" }, - "name": { - "type": "string", - "description": "Optional human-friendly job name shown in the history list." + "change_count": { + "type": "integer", + "description": "Number of changed fields (`update` only)." }, - "dry_run": { - "type": "boolean", - "default": false, - "description": "If true, the orchestrator runs Phase 0 + a planning pass and writes\n`would_*` resource rows but performs no destination writes.\n" + "changed_paths": { + "type": "array", + "description": "The first 20 changed field paths (`update` only).", + "maxItems": 20, + "items": { + "type": "string" + } }, - "include_dependencies": { - "type": "boolean", - "default": true, - "description": "If true, the orchestrator expands the resource set by following\ndependency edges discovered during Phase 0.\n" + "note": { + "type": "string", + "description": "Why the plan chose this action, when it is not obvious." }, - "resources": { + "depends_on": { "type": "array", - "description": "Initial resource selection. Dependencies may be added.", + "description": "Other resources of the plan this resource references.", "items": { - "type": "object", - "required": [ - "type", - "id" - ], - "properties": { - "type": { - "type": "string", - "description": "Resource type identifier" - }, - "id": { - "type": "string", - "description": "Source-org resource ID" - } - } + "$ref": "#/components/schemas/SyncJobResourceRef" } } - } - }, - "SyncJobRetryRequest": { - "type": "object", - "description": "Optional body for `retrySyncJob`. Defaults to retrying every unresolved\nresource of the original job (failed, pending or in_progress).\n", - "properties": { - "payload_overrides": { - "type": "object", - "description": "Map of `:` → partial payload patch. Applied on top of\nthe originally fetched payload before re-running Phase A.\n", - "additionalProperties": true - } - } + }, + "required": [ + "action", + "match" + ] }, - "SyncJobCounts": { + "SyncPlanCounts": { "type": "object", - "description": "Aggregate counters by resource status.", + "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", "properties": { "total": { "type": "integer" }, - "pending": { + "create": { "type": "integer" }, - "in_progress": { + "update": { "type": "integer" }, - "succeeded": { + "no_change": { + "type": "integer" + }, + "skip": { "type": "integer" }, "failed": { "type": "integer" }, - "skipped_unchanged": { + "not_compared": { "type": "integer" } }, "required": [ "total", - "pending", - "in_progress", - "succeeded", + "create", + "update", + "no_change", + "skip", "failed", - "skipped_unchanged" - ] - }, - "SyncJobEvent": { - "type": "object", - "description": "Activity-log entry surfaced to the frontend. Backed by the op-log rows in\nthe index table (`SYNC##OP#`).\n", - "properties": { - "seq": { - "type": "integer", - "description": "Monotonic sequence number assigned at write time." - }, - "ts": { - "type": "string", - "format": "date-time" - }, - "phase": { - "$ref": "#/components/schemas/SyncPhase" - }, - "type": { - "type": "string", - "description": "Resource type" - }, - "source_id": { - "type": "string" - }, - "target_id": { - "type": "string" - }, - "status": { - "$ref": "#/components/schemas/SyncJobResourceStatus" - }, - "message": { - "type": "string" - }, - "error": { - "type": "string" - } - }, - "required": [ - "seq", - "ts", - "status" - ] - }, - "SyncJobBatch": { - "type": "object", - "description": "Position within the current topological batch for the active phase.", - "properties": { - "index": { - "type": "integer", - "description": "Zero-based index of the batch currently executing." - }, - "of": { - "type": "integer", - "description": "Total number of batches in the current phase." - }, - "level": { - "type": "integer", - "description": "Dependency level (matches `dependencyLevel` from the topological sort)." - } - }, - "required": [ - "index", - "of", - "level" + "not_compared" ] }, - "SyncJob": { + "SyncPlan": { "type": "object", - "description": "Sync job header as surfaced by `getSyncJob` and the create response. The\ncanonical persistence shape is described in `docs/sync/INTERFACES.md`.\n", "properties": { - "id": { - "type": "string" - }, - "name": { + "job_id": { "type": "string" }, "status": { "$ref": "#/components/schemas/SyncJobStatus" }, - "direction": { - "$ref": "#/components/schemas/SyncDirection" + "ready": { + "type": "boolean", + "description": "True once planning has finished and produced a plan (the job\nended `succeeded` or `partial`). Stays false for a plan job that\nended `failed` or `cancelled`; its counts cover only the rows\nplanned before it stopped.\n" }, - "source_org_id": { - "type": "string" + "applicable": { + "type": "boolean", + "description": "True while `applySyncPlan` accepts the plan: it is `ready` and\n`expires_at` has not passed. A plan can be applied more than once\n(e.g. one part now, another later); each apply is listed in\n`applied_by`.\n" }, - "target_org_id": { - "type": "string" + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the plan stops being applicable: 24 hours after every row was\nlast planned (`planned_at`). A partial replan does not move it.\n" }, - "dry_run": { - "type": "boolean" + "planned_at": { + "type": "string", + "format": "date-time", + "description": "When every row was last planned (first run or full replan)." }, - "counts": { - "$ref": "#/components/schemas/SyncJobCounts" + "replanned_at": { + "type": "string", + "format": "date-time", + "description": "When a partial replan (`replanSyncPlan` with `resources`) last finished." }, - "current_phase": { - "$ref": "#/components/schemas/SyncPhase" + "source_org_id": { + "type": "string" }, - "current_batch": { - "$ref": "#/components/schemas/SyncJobBatch" + "target_org_id": { + "type": "string" }, "started_at": { "type": "string", @@ -1840,70 +3977,80 @@ "type": "string", "format": "date-time" }, - "events": { - "type": "array", - "description": "Most recent events (capped server-side, typically last 10).", - "items": { - "$ref": "#/components/schemas/SyncJobEvent" - } + "summary": { + "$ref": "#/components/schemas/SyncPlanCounts" }, - "errors_sample": { + "by_type": { "type": "array", - "description": "Up to 20 sample error messages from failed resources.", - "maxItems": 20, "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "source_id": { - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/SyncPlanCounts" }, - "error": { - "type": "string" + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] } - }, - "required": [ - "type", - "source_id", - "error" ] } + }, + "applied_by": { + "type": "array", + "description": "Ids of the jobs that applied this plan, oldest first.", + "items": { + "type": "string" + } } }, "required": [ - "id", + "job_id", "status", - "direction", + "ready", + "applicable", "source_org_id", "target_org_id", - "dry_run", - "counts", - "started_at" + "summary", + "by_type" ] }, - "SyncJobListResponse": { + "SyncPlanChange": { "type": "object", - "description": "Cursor-paginated list of sync jobs.", + "description": "One 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: ids of the resource itself, org ids, timestamps and audit\nfields (`created_at`, `updated_by`, `_manifest`, …). References to\nother resources are shown as target-org ids. A reference to a\nresource the plan would create is shown as `new::`,\nsince its target id does not exist yet.\n\nA value whose JSON is larger than 4 KB is replaced by\n`{ \"__truncated\": true, \"json_bytes\": }`.\n", "properties": { - "next_cursor": { + "path": { "type": "string" }, - "results": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SyncJob" - } + "kind": { + "type": "string", + "description": "`added`: only the source has a value here. `removed`: only the\ntarget has one (a real run does not necessarily delete it; that\ndepends on the resource's API). `changed`: both have different\nvalues. `reordered`: the same array items in a different order.\n", + "enum": [ + "added", + "removed", + "changed", + "reordered" + ] + }, + "before": { + "description": "The target org's current value. Absent for `added`." + }, + "after": { + "description": "The value a real run would write. Absent for `removed`." } }, "required": [ - "results" + "path", + "kind" ] }, - "SyncJobResource": { + "SyncPlanDiff": { "type": "object", - "description": "Per-resource row backed by `SyncResourcesTable`. See\n`docs/sync/INTERFACES.md` for the DDB shape.\n", "properties": { "type": { "type": "string" @@ -1912,25 +4059,40 @@ "type": "string" }, "target_id": { - "type": "string" + "type": "string", + "description": "The target counterpart. Absent for `create`." }, "title": { - "type": "string", - "description": "Human-readable resource title captured from the source payload\nduring Phase 0. Lets clients identify rows without resolving\nraw UUIDs. Absent on rows written before this field existed.\n" + "type": "string" }, - "status": { - "$ref": "#/components/schemas/SyncJobResourceStatus" + "action": { + "$ref": "#/components/schemas/SyncPlanAction" }, - "phase": { - "$ref": "#/components/schemas/SyncPhase" + "match": { + "$ref": "#/components/schemas/SyncPlanMatch" }, - "attempt": { - "type": "integer" + "diff_status": { + "$ref": "#/components/schemas/SyncPlanDiffStatus" }, - "error": { + "change_count": { + "type": "integer", + "description": "Number of changed fields, including any left out of `changes`." + }, + "changes": { + "type": "array", + "description": "The changed fields (at most 200). Empty for `no_change`. For\n`create` a single `added` change at `$` holds the whole resource.\n", + "items": { + "$ref": "#/components/schemas/SyncPlanChange" + } + }, + "truncated": { + "type": "boolean", + "description": "True when changes or values were left out to keep the diff small.\n`change_count` is still exact.\n" + }, + "note": { "type": "string" }, - "updated_at": { + "planned_at": { "type": "string", "format": "date-time" } @@ -1938,15 +4100,16 @@ "required": [ "type", "source_id", - "status", - "phase", - "attempt", - "updated_at" + "action", + "match", + "diff_status", + "change_count", + "changes", + "truncated" ] }, - "SyncJobResourceListResponse": { + "SyncPlanDiffListResponse": { "type": "object", - "description": "Cursor-paginated list of sync job resources.", "properties": { "next_cursor": { "type": "string" @@ -1954,7 +4117,7 @@ "results": { "type": "array", "items": { - "$ref": "#/components/schemas/SyncJobResource" + "$ref": "#/components/schemas/SyncPlanDiff" } } }, @@ -1962,6 +4125,139 @@ "results" ] }, + "SyncPlanApplyRequest": { + "type": "object", + "required": [ + "target_auth_token" + ], + "properties": { + "target_auth_token": { + "type": "string", + "writeOnly": true, + "description": "Destination-org auth token, as on `createSyncJob`. Never returned\nin responses and never logged.\n" + }, + "name": { + "type": "string", + "description": "Name of the apply job. Defaults to the plan's name." + }, + "resources": { + "type": "array", + "description": "Apply exactly these rows of the plan. Omit to apply every\n`create` and `update` row.\n", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + }, + "exclude": { + "type": "array", + "description": "Rows of the plan to leave out.", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + }, + "allow_missing_dependencies": { + "type": "boolean", + "default": false, + "description": "Apply even when left-out resources that would be created are\nreferenced by applied ones. Those references then keep pointing\nat the source org's ids.\n" + } + } + }, + "SyncPlanReplanRequest": { + "type": "object", + "required": [ + "target_auth_token" + ], + "properties": { + "target_auth_token": { + "type": "string", + "writeOnly": true, + "description": "Destination-org auth token (planning reads the target). Never returned or logged." + }, + "resources": { + "type": "array", + "description": "Rows of the plan to plan again. Omit to plan every row again.", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + } + } + }, + "SyncPlanMissingDependency": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "required_by": { + "type": "array", + "description": "Applied resources that reference it.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "type", + "source_id" + ] + } + } + }, + "required": [ + "type", + "source_id", + "required_by" + ] + }, + "SyncPlanApplyErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "error": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Machine-readable reason, for clients that show their own message.", + "enum": [ + "target_auth_token_missing", + "empty_selection", + "invalid_resources", + "plan_not_ready", + "plan_expired", + "nothing_to_apply", + "missing_dependencies" + ] + }, + "invalid_resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + }, + "missing_dependencies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SyncPlanMissingDependency" + } + } + } + }, "DeleteJobStatus": { "type": "string", "description": "Lifecycle status of a bulk-delete job.", @@ -2244,6 +4540,114 @@ "resources", "skipped_types" ] + }, + "CreatePreviewRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100, + "description": "Display name of the preview. Defaults to `Preview `." + }, + "requester": { + "type": "object", + "description": "Internal callers only (e.g. the admin portal, with the source org in `x-ivy-org-id`):\nwho gets invited to the preview org as owner; `user_id` or `email` is required. Users\nalways invite themselves; sending this with a user token is rejected with 403.\n", + "properties": { + "user_id": { + "type": "string", + "description": "A user of the source org (preferred). They are invited, and the copy job acts as\nthem (e.g. they own the copied dashboards). Email, name and language come from\ntheir user record.\n" + }, + "email": { + "type": "string", + "format": "email", + "description": "Used when `user_id` is not given." + }, + "display_name": { + "type": "string" + }, + "language": { + "type": "string", + "enum": [ + "de", + "en" + ] + } + } + } + } + }, + "PreviewStatus": { + "type": "string", + "enum": [ + "creating_org", + "copying_configuration", + "inviting", + "ready", + "failed" + ], + "description": "- 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" + }, + "Preview": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/PreviewStatus" + }, + "source_org_id": { + "type": "string", + "description": "The org the preview copies (the org it was requested from)." + }, + "preview_org_id": { + "type": "string", + "description": "The preview org, once created." + }, + "sync_job_id": { + "type": "string", + "description": "The sync job copying the configuration (see `getSyncJob`)." + }, + "sync_status": { + "type": "string", + "description": "Final status of the copy sync job (`succeeded` or `partial`)." + }, + "requested_by": { + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string", + "description": "Why the preview failed." + } + }, + "required": [ + "id", + "name", + "status", + "source_org_id", + "requested_by", + "created_at", + "updated_at" + ] } } } diff --git a/packages/cli/definitions/organization.json b/packages/cli/definitions/organization.json index 790926f6d..5550b8d9b 100644 --- a/packages/cli/definitions/organization.json +++ b/packages/cli/definitions/organization.json @@ -2,33 +2,25 @@ "openapi": "3.0.3", "info": { "title": "Organization API", - "description": "The Organization API provides endpoints for managing epilot tenant organizations.\n\n## Overview\n\nThis API allows you to:\n- **Retrieve organization details** - Get information about the current organization or a specific organization by ID\n- **Update organization profiles** - Modify organization name, contact information, address, and branding assets\n- **Manage organization settings** - Configure organization-wide settings such as feature flags and preferences\n- **Branding configuration** - Set organization logos, email signatures, and style settings\n\n## Organization Types\n\nOrganizations can be configured as:\n- **Production** - Live production environments for real business operations\n- **Sandbox** - Test environments linked to a parent production organization\n", + "description": "The Organization API provides endpoints for managing epilot tenant organizations.\n\n## Overview\n\nThis API allows you to:\n- **Retrieve organization details** - Get information about the current organization or a specific organization by ID\n- **Update organization profiles** - Modify organization name, contact information, address, and branding assets\n- **Manage organization settings** - Configure organization-wide settings such as feature flags and preferences\n- **Branding configuration** - Set organization logos, email signatures, and style settings\n\n## Organization Types\n\nOrganizations can be configured as:\n- **Production** - Live production environments for real business operations\n- **Sandbox** - Test environments linked to a parent production organization\n- **Preview** - Temporary copies of a production organization, used to preview configuration changes\n", "version": "1.1.0" }, "tags": [ { "name": "Organization", - "description": "Endpoints for managing organization profiles and information.\n\nOrganizations represent tenant accounts in the epilot platform. Each organization has:\n- Basic profile information (name, contact details, address)\n- Branding assets (logo, email signature)\n- Configuration settings\n- Usage type (Production or Sandbox)\n" + "description": "Endpoints for managing organization profiles and information.\n\nOrganizations represent tenant accounts in the epilot platform. Each organization has:\n- Basic profile information (name, contact details, address)\n- Branding assets (logo, email signature)\n- Configuration settings\n- Usage type (Production, Sandbox or Preview)\n" }, { "name": "Organization Settings", "description": "Endpoints for managing organization-wide configuration settings.\n\nSettings are stored as key-value pairs and can include:\n- Feature flags (e.g., double_opt_in)\n- Integration configurations\n- Platform preferences\n\nSettings values can be strings, numbers, booleans, arrays, or objects.\n" }, - { - "name": "Cleanup", - "description": "Internal endpoints for organization cleanup operations.\n\nThese endpoints are used by internal services to manage the deletion and cleanup\nof organization data across the platform. They track cleanup status per service\nto ensure complete data removal.\n" - }, - { - "name": "Internal", - "description": "Internal endpoints for platform administration.\n\nThese endpoints are not part of the public API and are used by internal epilot\nservices for administrative operations such as organization provisioning,\ndata aggregation, and cross-organization queries.\n" - }, - { - "name": "HubSpot", - "description": "Internal endpoints for HubSpot CRM integration.\n\nThese endpoints provide access to HubSpot company data synchronized with the\nepilot data warehouse for organization mapping and CRM integration purposes.\n" - }, { "name": "Feature Settings", "description": "Feature flag metadata for the organization settings page.\n\nThis metadata is used by frontend applications to render feature toggles\nwithout hardcoding titles, descriptions, badges, and visibility rules in the UI.\n" + }, + { + "name": "Announcements", + "description": "Platform announcements shown as a non-blocking portal banner, e.g. subprocessor change notices.\n\nAnnouncements are managed internally (admin portal) and acknowledged by organization admins.\nEach organization acknowledges an announcement at most once (first acknowledgement wins).\n" } ], "security": [ @@ -40,6 +32,11 @@ "/v2/organization/current": { "get": { "operationId": "getCurrentOrganization", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "summary": "getCurrentOrganization", "description": "Retrieves the organization associated with the authenticated user's current session.\n\nThis endpoint is useful for fetching organization details without knowing the organization ID,\nas it automatically resolves the organization from the authentication token.\n", "tags": [ @@ -86,6 +83,11 @@ "/v2/organization/{org_id}": { "get": { "operationId": "getOrganization", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "summary": "getOrganization", "description": "Retrieves detailed information about a specific organization by its unique identifier.\n\nReturns the organization's profile information including name, contact details,\naddress, branding assets, and configuration.\n", "tags": [ @@ -144,6 +146,11 @@ }, "patch": { "operationId": "updateOrganization", + "x-epilot-permissions": [ + { + "action": "organization:edit" + } + ], "summary": "updateOrganization", "description": "Updates an organization's profile information.\n\nThis endpoint supports partial updates - only the fields provided in the request body\nwill be modified. Fields not included in the request will retain their current values.\n\nCommon use cases:\n- Updating contact information (email, phone, website)\n- Changing the organization name\n- Updating the postal address\n- Setting or updating branding assets (logo_url, logo_thumbnail_url)\n- Configuring the default email signature\n", "tags": [ @@ -215,6 +222,11 @@ "/v2/organization/{org_id}/settings": { "get": { "operationId": "getSettings", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "summary": "getSettings", "description": "Retrieves all configuration settings for an organization.\n\nReturns a key-value map of all settings configured for the organization.\nSettings control various platform features and behaviors including:\n- Feature flags (e.g., double_opt_in, email_tracking)\n- Integration configurations\n- UI preferences\n- Workflow settings\n", "tags": [ @@ -275,6 +287,14 @@ "/v2/organization/{org_id}/settings/{key}": { "put": { "operationId": "putSettingsValue", + "x-epilot-agent": { + "expose": false + }, + "x-epilot-permissions": [ + { + "action": "organization:settings" + } + ], "summary": "putSettingsValue", "description": "Creates or updates a specific organization setting identified by its key.\n\nThe setting value can be any valid JSON type:\n- String (e.g., \"en\", \"production\")\n- Number (e.g., 100, 3.14)\n- Boolean (e.g., true, false)\n- Object (e.g., {\"enabled\": true, \"threshold\": 50})\n- Array (e.g., [\"email\", \"sms\", \"push\"])\n\nIf the setting key does not exist, it will be created. If it exists, its value\nwill be replaced with the new value.\n", "tags": [ @@ -362,6 +382,14 @@ }, "delete": { "operationId": "deleteSettingsValue", + "x-epilot-agent": { + "expose": false + }, + "x-epilot-permissions": [ + { + "action": "organization:settings" + } + ], "summary": "deleteSettingsValue", "description": "Removes a specific organization setting identified by its key.\n\nAfter deletion, the setting will no longer appear in the organization's settings\nand any features relying on this setting will use their default behavior.\n", "tags": [ @@ -404,11 +432,99 @@ } } }, + "/v2/organization/{org_id}/settings/{key}/notifications": { + "post": { + "operationId": "notifySettingsValueUpdated", + "x-epilot-permissions": [ + { + "action": "organization:settings" + } + ], + "summary": "notifySettingsValueUpdated", + "description": "Sends an internal notification that an organization setting was updated.", + "tags": [ + "Organization Settings" + ], + "parameters": [ + { + "in": "path", + "name": "org_id", + "schema": { + "$ref": "#/components/schemas/OrganizationId" + }, + "required": true, + "description": "The unique identifier of the organization" + }, + { + "in": "path", + "name": "key", + "schema": { + "type": "string" + }, + "required": true, + "description": "The setting key that was updated", + "example": "360_features" + } + ], + "requestBody": { + "description": "Notification payload for the updated setting", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user_email": { + "type": "string", + "description": "Email of the staff user who changed the setting" + }, + "org_name": { + "type": "string", + "description": "Organization display name" + }, + "value": { + "$ref": "#/components/schemas/SettingsValue" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Notification processed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "delivered": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + }, + "403": { + "description": "Forbidden - User does not have permission to notify for organization settings" + } + } + } + }, "/v2/organization/feature-settings": { "get": { "operationId": "getOrganizationFeatureSettings", - "summary": "Get organization feature settings", - "description": "Returns feature flag metadata for the organization UI.\n\nThis data is static (identical for all organizations) and changes infrequently.\nClients should cache responses using the Cache-Control header.\n", + "x-epilot-permissions": [ + { + "access": "org" + } + ], + "summary": "getOrganizationFeatureSettings", + "description": "Get organization feature settings.\n\nReturns feature flag metadata for the organization UI.\n\nThis data is static (identical for all organizations) and changes infrequently.\nClients should cache responses using the Cache-Control header.\n", "tags": [ "Feature Settings" ], @@ -440,9 +556,14 @@ "/v2/feature-settings": { "get": { "operationId": "getFeatureSettings", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "deprecated": true, - "summary": "Get platform configuration metadata", - "description": "Deprecated: use /v2/organization/feature-settings instead.", + "summary": "getFeatureSettings", + "description": "Get platform configuration metadata.\n\nDeprecated: use /v2/organization/feature-settings instead.\n", "tags": [ "Feature Settings" ], @@ -462,6 +583,97 @@ } } } + }, + "/v2/organization/announcements/active": { + "get": { + "operationId": "getActiveAnnouncement", + "x-epilot-permissions": [ + { + "access": "org" + } + ], + "summary": "getActiveAnnouncement", + "description": "Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the\ncaller's organization.\n\nOnly the newest published announcement whose acknowledgement window is still open is returned, and\nonly if the organization is eligible (created before the announcement was published) and has not\nacknowledged it yet. Otherwise `announcement` is `null`.\n\n`can_acknowledge` tells whether the caller may acknowledge (requires `organization:edit`).\n", + "tags": [ + "Announcements" + ], + "responses": { + "200": { + "description": "Active announcement for the organization, or null", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActiveAnnouncementResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + } + } + } + }, + "/v2/organization/announcements/{announcement_id}/acknowledge": { + "post": { + "operationId": "acknowledgeAnnouncement", + "x-epilot-permissions": [ + { + "action": "organization:edit" + } + ], + "summary": "acknowledgeAnnouncement", + "description": "Acknowledges an announcement on behalf of the caller's organization. Only org admins\n(`organization:edit`) may acknowledge.\n\nOne acknowledgement per organization: the first acknowledgement wins. Acknowledging an already\nacknowledged announcement returns the existing record.\n", + "tags": [ + "Announcements" + ], + "parameters": [ + { + "$ref": "#/components/parameters/AnnouncementIdPathParam" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "portal": { + "type": "string", + "description": "Portal the acknowledgement was given in", + "example": "360" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Acknowledgement record of the organization (new or existing)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnnouncementOrganizationStatus" + } + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + }, + "403": { + "description": "Forbidden - Caller is missing the organization:edit permission" + }, + "404": { + "description": "Announcement not found or organization not eligible" + }, + "409": { + "description": "Announcement window closed, announcement superseded or archived" + } + } + } } }, "components": { @@ -473,6 +685,17 @@ "bearerFormat": "JWT" } }, + "parameters": { + "AnnouncementIdPathParam": { + "in": "path", + "name": "announcement_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Announcement id" + } + }, "schemas": { "OrganizationId": { "type": "string", @@ -539,10 +762,11 @@ }, "organization_use": { "type": "string", - "description": "Indicates whether this is a production or sandbox organization.\nSandbox organizations are linked to a parent production organization for testing purposes.\n", + "description": "Indicates whether this is a production, sandbox or preview organization.\nSandbox organizations are linked to a parent production organization for testing purposes.\nPreview organizations are temporary copies of a production organization.\n", "enum": [ "Production", - "Sandbox" + "Sandbox", + "Preview" ], "nullable": true }, @@ -626,19 +850,6 @@ } } }, - "InternalOrganization": { - "description": "Extended organization object with internal-only fields.\n\nIncludes all fields from the base Organization schema plus additional\ninternal metadata used for platform administration and support.\n", - "allOf": [ - { - "$ref": "#/components/schemas/Organization" - } - ] - }, - "SettingKey": { - "type": "string", - "description": "A unique key identifying an organization setting.\nCommon setting keys include: double_opt_in, email_tracking, default_language, workflow_notifications\n", - "example": "double_opt_in" - }, "Settings": { "type": "object", "description": "A key-value map of all organization settings.\nKeys are setting identifiers and values can be any valid JSON type.\n", @@ -682,357 +893,6 @@ "enabled": true } }, - "CreateOrganizationRequest": { - "type": "object", - "description": "Request payload for creating a new organization", - "properties": { - "organization_detail": { - "description": "Core organization information for the new tenant", - "type": "object", - "required": [ - "name", - "type", - "pricing_tier_id" - ], - "properties": { - "name": { - "description": "Display name for the organization", - "type": "string", - "example": "epilot" - }, - "email_address": { - "description": "Primary contact email for the organization", - "type": "string", - "example": "epilot@epilot.cloud" - }, - "type": { - "description": "Organization type classification.\n- Vendor: Primary organization type for epilot customers\n- Partner: Partner organization type\n", - "type": "string", - "example": "Vendor" - }, - "organization_use": { - "description": "Whether this is a production or sandbox environment.\nSandbox organizations are for testing and linked to a production org.\n", - "type": "string", - "enum": [ - "Production", - "Sandbox" - ] - }, - "pricing_tier_id": { - "description": "The ULID of the pricing tier to assign to this organization.\nDetermines feature access and billing.\n", - "type": "string", - "example": "01GEKHZHSN19KK10ZS92Y3WY9B" - } - } - }, - "owner_user": { - "description": "Initial owner user details.\nThis user will receive an invitation email to complete registration\nand become the organization administrator.\n", - "type": "object", - "required": [ - "email_address" - ], - "properties": { - "full_name": { - "description": "Full name of the owner user", - "type": "string", - "example": "Ny Huynh" - }, - "email_address": { - "description": "Email address for the invitation (must be unique in the platform)", - "type": "string", - "example": "ny.huynhthi@axonactive.com" - } - } - } - } - }, - "DataPointsResponse": { - "type": "array", - "description": "List of data point metrics for all organizations", - "items": { - "$ref": "#/components/schemas/DataPoint" - } - }, - "DataPoint": { - "type": "object", - "description": "Data point metrics for a single organization, used for usage tracking and billing", - "properties": { - "id": { - "description": "The organization ID (numeric format)", - "type": "number", - "example": 206801 - }, - "max_customer": { - "description": "Maximum number of customer entities recorded in the previous month.\nUsed as a high-water mark for billing calculations.\n", - "type": "number", - "example": 10 - }, - "actual_customer": { - "description": "Current count of customer entities in the organization.\nRepresents the actual usage at the time of the query.\n", - "type": "number", - "example": 10 - } - } - }, - "OrganizationToCleanup": { - "type": "object", - "description": "Represents an organization that has been marked for deletion and requires cleanup.\nContains metadata about the deletion request.\n", - "required": [ - "org_id", - "deleted_at", - "deleted_by" - ], - "properties": { - "org_id": { - "$ref": "#/components/schemas/OrganizationId" - }, - "deleted_at": { - "description": "Timestamp when the organization was marked for deletion", - "type": "string", - "format": "date-time", - "example": "2021-06-01T00:00:00Z" - }, - "deleted_by": { - "description": "User ID of the person who initiated the deletion", - "type": "string", - "example": "123456" - } - } - }, - "OrganizationCleanupStatus": { - "type": "object", - "description": "Records the cleanup status reported by a specific service for an organization.\nEach service that stores organization data reports its cleanup operations here.\n", - "required": [ - "org_id", - "service_name" - ], - "properties": { - "org_id": { - "$ref": "#/components/schemas/OrganizationId" - }, - "service_name": { - "description": "Name of the service reporting its cleanup status", - "type": "string", - "example": "organization-api" - }, - "operations": { - "description": "List of cleanup operations performed by the service.\nEach operation describes what action was taken on which resource.\n", - "type": "array", - "items": { - "type": "object", - "additionalProperties": true, - "properties": { - "action": { - "description": "The type of cleanup action performed.\nCommon values: Delete, Archive, Purge\n", - "type": "string", - "example": "Delete" - }, - "resource": { - "description": "The resource that was cleaned up.\nCan be a table name, S3 bucket, or other resource identifier.\n", - "type": "string", - "example": "OrgTable" - }, - "extra_info": { - "description": "Additional context about the cleanup operation", - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - { - "type": "object", - "additionalProperties": true - } - ] - } - } - }, - "example": [ - { - "action": "Delete", - "resource": "OrgTable", - "extra_info": "pk: ORG#739224" - }, - { - "action": "Archive", - "resource": "s3://my-bucket", - "extra_info": "my-bucket/my-key.txt" - } - ] - } - } - }, - "HubspotCompany": { - "type": "object", - "description": "Represents a company record from HubSpot CRM synchronized to the epilot data warehouse.\nUsed for CRM integration and organization mapping.\n", - "properties": { - "company_id": { - "type": "string", - "description": "The unique HubSpot company identifier", - "example": "12345678901" - }, - "company_name": { - "type": "string", - "nullable": true, - "description": "The display name of the company in HubSpot", - "example": "Acme Corp" - }, - "domain": { - "type": "string", - "nullable": true, - "description": "The primary web domain associated with the company", - "example": "acme.com" - } - } - }, - "HubspotCompaniesResponse": { - "type": "object", - "description": "Response containing a list of HubSpot companies matching the search criteria", - "properties": { - "results": { - "type": "array", - "description": "List of HubSpot companies matching the query", - "items": { - "$ref": "#/components/schemas/HubspotCompany" - } - }, - "total": { - "type": "integer", - "description": "Total number of companies in the response", - "example": 10 - } - } - }, - "HubspotOrganizationData": { - "type": "object", - "description": "HubSpot company data associated with an epilot organization.\nContains business metrics and CRM properties synced from HubSpot.\n", - "properties": { - "company_id": { - "type": "string", - "description": "The unique HubSpot company identifier", - "example": "5278308807" - }, - "company_name": { - "type": "string", - "nullable": true, - "description": "The display name of the company in HubSpot", - "example": "Stadtwerke Meerbusch Willich Netz" - }, - "domain": { - "type": "string", - "nullable": true, - "description": "The primary web domain associated with the company", - "example": "stadtwerke-service.de" - }, - "current_mrr": { - "type": "string", - "nullable": true, - "description": "Current Monthly Recurring Revenue", - "example": "11033.33" - }, - "potential_mrr": { - "type": "string", - "nullable": true, - "description": "Potential Monthly Recurring Revenue", - "example": "20000" - }, - "company_size": { - "type": "string", - "nullable": true, - "description": "Company size classification", - "example": "Large" - }, - "pricing_tier": { - "type": "string", - "nullable": true, - "description": "Current pricing tier", - "example": "Professional" - }, - "lifecyclestage": { - "type": "string", - "nullable": true, - "description": "HubSpot lifecycle stage", - "example": "customer" - }, - "industry": { - "type": "string", - "nullable": true, - "description": "Industry classification", - "example": "Energiedienstleister" - }, - "numberofemployees": { - "type": "string", - "nullable": true, - "description": "Number of employees", - "example": "200" - }, - "customer_number": { - "type": "string", - "nullable": true, - "description": "epilot customer number", - "example": "10031" - }, - "no_of_users": { - "type": "string", - "nullable": true, - "description": "Number of active users", - "example": "34" - }, - "activation_rate": { - "type": "string", - "nullable": true, - "description": "User activation rate", - "example": "0.647059" - }, - "active_features": { - "type": "string", - "nullable": true, - "description": "List of active features", - "example": "Installer Portal" - }, - "usecases_implemented": { - "type": "string", - "nullable": true, - "description": "Implemented use cases", - "example": "GRID: Installations (MGA);GRID: House Connections" - }, - "buyer_journey": { - "type": "string", - "nullable": true, - "description": "Current buyer's journey stage", - "example": "Expansion-Ready" - }, - "country": { - "type": "string", - "nullable": true, - "description": "Company country", - "example": "Germany" - }, - "city": { - "type": "string", - "nullable": true, - "description": "Company city", - "example": "Willich" - }, - "properties": { - "type": "object", - "additionalProperties": true, - "description": "Full HubSpot properties object with all available fields" - } - } - }, "FeatureSettings": { "type": "object", "description": "Feature settings metadata served to frontend applications.", @@ -1145,6 +1005,8 @@ "never", "advanced_mode", "pricing_tier", + "pricing_tier_class", + "production_org", "feature_flag", "permission", "setting_enabled", @@ -1162,6 +1024,17 @@ "type": "string", "description": "Flag name for feature_flag and flag_enabled rules" }, + "pricing_tier_classes": { + "type": "array", + "description": "Pricing tier class names matched (word-boundary, case-insensitive) for pricing_tier_class rules", + "items": { + "type": "string" + }, + "example": [ + "enterprise", + "professional" + ] + }, "action": { "type": "string", "description": "Permission action for permission rules" @@ -1181,6 +1054,200 @@ } } } + }, + "AnnouncementType": { + "type": "string", + "enum": [ + "subprocessor_change" + ] + }, + "AnnouncementMessage": { + "type": "object", + "required": [ + "de" + ], + "properties": { + "de": { + "type": "string", + "minLength": 1, + "example": "Wir haben unsere Liste der Subdienstleister aktualisiert (gültig ab 01.11.2026). Bitte prüfe die Änderungen." + }, + "en": { + "type": "string", + "example": "We have updated our list of subprocessors (effective 01.11.2026). Please review the changes." + } + } + }, + "Announcement": { + "type": "object", + "required": [ + "id", + "type", + "message", + "link_url", + "list_version", + "effective_date", + "window_days", + "status", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "$ref": "#/components/schemas/AnnouncementType" + }, + "message": { + "$ref": "#/components/schemas/AnnouncementMessage" + }, + "link_url": { + "type": "string" + }, + "list_version": { + "type": "string" + }, + "effective_date": { + "type": "string", + "format": "date" + }, + "window_days": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published", + "archived" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string", + "description": "Internal caller that created the announcement (not returned to customers)" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "published_at": { + "type": "string", + "format": "date-time" + }, + "published_by": { + "type": "string", + "description": "Internal caller that published the announcement (not returned to customers)" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "published_at + window_days" + }, + "archived_at": { + "type": "string", + "format": "date-time" + }, + "archived_by": { + "type": "string" + }, + "materialized_at": { + "type": "string", + "format": "date-time", + "description": "When the daily job wrote expired/superseded records after the window closed" + } + } + }, + "AnnouncementOrganizationStatusValue": { + "type": "string", + "enum": [ + "acknowledged", + "pending", + "expired", + "superseded" + ] + }, + "AnnouncementOrganizationStatus": { + "type": "object", + "required": [ + "announcement_id", + "org_id", + "status", + "list_version" + ], + "properties": { + "announcement_id": { + "type": "string" + }, + "org_id": { + "type": "string" + }, + "org_name": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AnnouncementOrganizationStatusValue" + }, + "acknowledged_by": { + "type": "object", + "required": [ + "user_id" + ], + "properties": { + "user_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "acknowledged_at": { + "type": "string", + "format": "date-time" + }, + "portal": { + "type": "string", + "example": "360" + }, + "list_version": { + "type": "string" + }, + "recorded_at": { + "type": "string", + "format": "date-time", + "description": "When the record was written (acknowledgement or materialization by the daily job)" + } + } + }, + "ActiveAnnouncementResponse": { + "type": "object", + "required": [ + "announcement", + "can_acknowledge" + ], + "properties": { + "announcement": { + "allOf": [ + { + "$ref": "#/components/schemas/Announcement" + } + ], + "nullable": true + }, + "can_acknowledge": { + "type": "boolean", + "description": "Whether the caller may acknowledge (organization:edit)" + } + } } } }, diff --git a/packages/cli/docs/configuration-hub.md b/packages/cli/docs/configuration-hub.md index 8a1d3bc50..efc5e7a11 100644 --- a/packages/cli/docs/configuration-hub.md +++ b/packages/cli/docs/configuration-hub.md @@ -44,6 +44,13 @@ epilot configuration-hub listConfigTypes - [`rebuildIndex`](#rebuildindex) — Rebuild the configuration index for the caller's organization. - [`getConfigInventory`](#getconfiginventory) — Returns a fresh inventory of an org's configuration resources — `{ type, id }` identities only, +**Health** +- [`startHealthScan`](#starthealthscan) — Start an asynchronous org health scan — fire-and-forget, mirrors +- [`getHealthReport`](#gethealthreport) — Return the caller org's current health report: scan status, summary +- [`requestHealthCoverage`](#requesthealthcoverage) — Send a request for configuration the health check does not cover yet. +- [`startHealthCleanup`](#starthealthcleanup) — Remove references that are already inert: dangling `$relation` entries +- [`setHealthFindingsIgnored`](#sethealthfindingsignored) — Acknowledge findings as intentional (or undo that). An ignored + **Compare** - [`compareConfigs`](#compareconfigs) — Compare the caller org's configs of a single type against another - [`suggestMatches`](#suggestmatches) — Run the sync-grade heuristic match (`lookupByHeuristic` — name / slug / @@ -54,9 +61,18 @@ epilot configuration-hub listConfigTypes - [`listSyncJobs`](#listsyncjobs) — List sync jobs scoped to the caller's organization, paginated with an opaque - [`createSyncJob`](#createsyncjob) — Create a new cross-org sync job. The job is enqueued for asynchronous execution - [`getSyncJob`](#getsyncjob) — Fetch a single sync job by ID. Returns the job header, counts summary, +- [`getSyncJobHealth`](#getsyncjobhealth) — Targeted health check of an applied (non-dry-run) sync job. When the job +- [`runSyncJobHealth`](#runsyncjobhealth) — Start the targeted health check of a finished, applied sync job again: - [`retrySyncJob`](#retrysyncjob) — Retry the unresolved resources from a prior sync job: `failed` rows, plus +- [`planSyncJobRetry`](#plansyncjobretry) — Preview what `retrySyncJob` would re-run for the same `resources`, - [`cancelSyncJob`](#cancelsyncjob) — Cancel a running sync job. Marks the job `cancelled` with a `finished_at` +- [`listSyncJobEvents`](#listsyncjobevents) — Full activity log of a sync job, latest first: one event per resource - [`listSyncJobResources`](#listsyncjobresources) — List the per-resource rows for a sync job. Supports filtering by status +- [`getSyncPlan`](#getsyncplan) — Summary of a plan: a dry-run sync job (`createSyncJob` with +- [`listSyncPlanDiffs`](#listsyncplandiffs) — Field-level diffs of a plan, one per planned resource: what the target +- [`getSyncPlanDiff`](#getsyncplandiff) — Field-level diff of one planned resource. See `listSyncPlanDiffs`. +- [`applySyncPlan`](#applysyncplan) — Run a finished plan for real. Creates a new sync job (it carries +- [`replanSyncPlan`](#replansyncplan) — Plan some rows of a plan again, in place, e.g. after fixing a **Delete** - [`listDeleteJobs`](#listdeletejobs) — List bulk-delete jobs scoped to the caller's organization, paginated @@ -64,6 +80,11 @@ epilot configuration-hub listConfigTypes - [`getDeleteJob`](#getdeletejob) — Fetch a single bulk-delete job by ID. Returns the job header and counts - [`listDeleteJobResources`](#listdeletejobresources) — List the per-resource rows for a delete job, cursor-paginated. Used by +**Previews** +- [`listPreviews`](#listpreviews) — Lists the previews requested from the caller's current org, newest first (max 50). +- [`createPreview`](#createpreview) — Starts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`, +- [`getPreview`](#getpreview) — Returns a preview requested from the caller's current org, with its progress. + ### `listConfigTypes` Returns the static list of available configuration types with display metadata. @@ -114,7 +135,7 @@ List configs of a given type with pagination. Returns summary metadata only | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `type` | path | "journey" \| "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" | Yes | Configuration resource type | +| `type` | path | "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" | Yes | Configuration resource type | | `cursor` | query | string | No | Opaque cursor for fetching the next page. Omit for the first page. | | `size` | query | number | No | Number of items per page | | `q` | query | string | No | Search query to filter configs by name/title | @@ -196,7 +217,7 @@ Get configs that are referenced by the given config. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `type` | path | "journey" \| "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" | Yes | Configuration resource type | +| `type` | path | "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" | Yes | Configuration resource type | | `id` | path | string | Yes | Configuration resource ID | | `cursor` | query | string | No | Opaque cursor for fetching the next page. Omit for the first page. | | `size` | query | number | No | Number of items per page | @@ -266,7 +287,7 @@ Get configs that reference the given config (reverse dependencies). | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `type` | path | "journey" \| "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" | Yes | Configuration resource type | +| `type` | path | "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" | Yes | Configuration resource type | | `id` | path | string | Yes | Configuration resource ID | **Sample Call** @@ -359,6 +380,207 @@ epilot configuration-hub getIndex --jsonata 'status' --- +### `startHealthScan` + +Start an asynchronous org health scan — fire-and-forget, mirrors + +`POST /v1/configs/health:scan` + +**Sample Call** + +```bash +epilot configuration-hub startHealthScan +``` + +With JSONata filter: + +```bash +epilot configuration-hub startHealthScan --jsonata '$' +``` + +--- + +### `getHealthReport` + +Return the caller org's current health report: scan status, summary + +`GET /v1/configs/health` + +**Sample Call** + +```bash +epilot configuration-hub getHealthReport +``` + +With JSONata filter: + +```bash +epilot configuration-hub getHealthReport --jsonata 'status' +``` + +
+Sample Response + +```json +{ + "status": "missing", + "last_scanned_at": "1970-01-01T00:00:00.000Z", + "scan_duration_ms": 0, + "scanned_configs": 0, + "scanned_by_type": {}, + "counts": { + "error": 0, + "warning": 0, + "info": 0 + }, + "new_since_previous": 0, + "resolved_since_previous": 0, + "last_cleanup_at": "1970-01-01T00:00:00.000Z", + "cleaned_count": 0, + "history": [ + { + "scanned_at": "1970-01-01T00:00:00.000Z", + "clean": 0, + "total": 0 + } + ], + "error": "string", + "findings": [ + { + "id": "string", + "code": "broken_reference", + "severity": "error", + "source_type": "string", + "source_id": "string", + "source_title": "string", + "message": "string", + "message_key": "string", + "message_params": {}, + "ref_type": "string", + "ref_id": "string", + "ref_title": "string", + "source_link": "string", + "ref_link": "string", + "owner_type": "string", + "owner_id": "string", + "owner_title": "string", + "owner_link": "string", + "path": "string", + "cleanable": true, + "ignored": true, + "ignored_at": "1970-01-01T00:00:00.000Z", + "ignored_by": "string", + "ignored_reason": "string", + "first_seen": "1970-01-01T00:00:00.000Z", + "last_seen": "1970-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `requestHealthCoverage` + +Send a request for configuration the health check does not cover yet. + +`POST /v1/configs/health:coverage-request` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot configuration-hub requestHealthCoverage \ + -d '{"message":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot configuration-hub requestHealthCoverage +``` + +With JSONata filter: + +```bash +epilot configuration-hub requestHealthCoverage --jsonata '$' +``` + +--- + +### `startHealthCleanup` + +Remove references that are already inert: dangling `$relation` entries + +`POST /v1/configs/health:cleanup` + +**Request Body** + +**Sample Call** + +```bash +epilot configuration-hub startHealthCleanup \ + -d '{"finding_ids":["string"]}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot configuration-hub startHealthCleanup +``` + +With JSONata filter: + +```bash +epilot configuration-hub startHealthCleanup --jsonata '$' +``` + +--- + +### `setHealthFindingsIgnored` + +Acknowledge findings as intentional (or undo that). An ignored + +`POST /v1/configs/health:ignore` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot configuration-hub setHealthFindingsIgnored \ + -d '{"finding_ids":["string"],"ignored":true,"reason":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot configuration-hub setHealthFindingsIgnored +``` + +With JSONata filter: + +```bash +epilot configuration-hub setHealthFindingsIgnored --jsonata 'status' +``` + +
+Sample Response + +```json +{ + "status": "ok", + "count": 0 +} +``` + +
+ +--- + ### `compareConfigs` Compare the caller org's configs of a single type against another @@ -560,7 +782,7 @@ Delete a lineage entry from the caller org's partition (caller as sync | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `type` | query | "journey" \| "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" | Yes | Configuration resource type of the lineage entry | +| `type` | query | "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" | Yes | Configuration resource type of the lineage entry | | `source_id` | query | string | Yes | Source-org config id the lineage entry is keyed by | **Sample Call** @@ -631,42 +853,26 @@ epilot configuration-hub listSyncJobs --jsonata 'results[0]' "source_org_id": "string", "target_org_id": "string", "dry_run": true, - "counts": { - "total": 0, - "pending": 0, - "in_progress": 0, - "succeeded": 0, - "failed": 0, - "skipped_unchanged": 0 - }, + "counts": {}, "current_phase": "phase_0", - "current_batch": { - "index": 0, - "of": 0, - "level": 0 - }, + "current_batch": {}, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", - "events": [ - { - "seq": 0, - "ts": "1970-01-01T00:00:00.000Z", - "phase": "phase_0", - "type": "string", - "source_id": "string", - "target_id": "string", - "status": "pending", - "message": "string", - "error": "string" - } - ], - "errors_sample": [ - { - "type": "string", - "source_id": "string", - "error": "string" - } - ] + "health_check": {}, + "events": [], + "created_by": "string", + "retry_of": "string", + "retried_by": ["string"], + "plan_only": true, + "plan_summary": {}, + "planned_at": "1970-01-01T00:00:00.000Z", + "expires_at": "1970-01-01T00:00:00.000Z", + "applied_from_plan": "string", + "applied_by": ["string"], + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "approvals": [], + "errors_sample": [] } ] } @@ -701,6 +907,7 @@ epilot configuration-hub createSyncJob \ "name": "string", "dry_run": false, "include_dependencies": true, + "sync_portal_widgets": true, "resources": [ { "type": "string", @@ -750,17 +957,56 @@ epilot configuration-hub createSyncJob --jsonata 'id' }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { + "id": "string", + "kind": "resource", "seq": 0, "ts": "1970-01-01T00:00:00.000Z", "phase": "phase_0", "type": "string", "source_id": "string", "target_id": "string", + "title": "string", "status": "pending", + "attempt": 0, "message": "string", - "error": "string" + "error": "string", + "dependency": {} + } + ], + "created_by": "string", + "retry_of": "string", + "retried_by": ["string"], + "plan_only": true, + "plan_summary": { + "total": 0, + "create": 0, + "update": 0, + "no_change": 0, + "skip": 0, + "failed": 0, + "not_compared": 0 + }, + "planned_at": "1970-01-01T00:00:00.000Z", + "expires_at": "1970-01-01T00:00:00.000Z", + "applied_from_plan": "string", + "applied_by": ["string"], + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "approvals": [ + { + "job_id": "string", + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z" } ], "errors_sample": [ @@ -836,17 +1082,56 @@ epilot configuration-hub getSyncJob -p id=123e4567-e89b-12d3-a456-426614174000 - }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { + "id": "string", + "kind": "resource", "seq": 0, "ts": "1970-01-01T00:00:00.000Z", "phase": "phase_0", "type": "string", "source_id": "string", "target_id": "string", + "title": "string", "status": "pending", + "attempt": 0, "message": "string", - "error": "string" + "error": "string", + "dependency": {} + } + ], + "created_by": "string", + "retry_of": "string", + "retried_by": ["string"], + "plan_only": true, + "plan_summary": { + "total": 0, + "create": 0, + "update": 0, + "no_change": 0, + "skip": 0, + "failed": 0, + "not_compared": 0 + }, + "planned_at": "1970-01-01T00:00:00.000Z", + "expires_at": "1970-01-01T00:00:00.000Z", + "applied_from_plan": "string", + "applied_by": ["string"], + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "approvals": [ + { + "job_id": "string", + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z" } ], "errors_sample": [ @@ -863,6 +1148,122 @@ epilot configuration-hub getSyncJob -p id=123e4567-e89b-12d3-a456-426614174000 - --- +### `getSyncJobHealth` + +Targeted health check of an applied (non-dry-run) sync job. When the job + +`GET /v1/configs/sync-jobs/{id}/health` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | + +**Sample Call** + +```bash +epilot configuration-hub getSyncJobHealth \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub getSyncJobHealth 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub getSyncJobHealth -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'status' +``` + +
+Sample Response + +```json +{ + "status": "pending", + "started_at": "1970-01-01T00:00:00.000Z", + "finished_at": "1970-01-01T00:00:00.000Z", + "scanned": 0, + "counts": { + "error": 0, + "warning": 0, + "info": 0 + }, + "error": "string", + "findings": [ + { + "id": "string", + "code": "broken_reference", + "severity": "error", + "source_type": "string", + "source_id": "string", + "source_title": "string", + "message": "string", + "message_key": "string", + "message_params": {}, + "ref_type": "string", + "ref_id": "string", + "ref_title": "string", + "source_link": "string", + "ref_link": "string", + "owner_type": "string", + "owner_id": "string", + "owner_title": "string", + "owner_link": "string", + "path": "string", + "cleanable": true, + "ignored": true, + "ignored_at": "1970-01-01T00:00:00.000Z", + "ignored_by": "string", + "ignored_reason": "string", + "first_seen": "1970-01-01T00:00:00.000Z", + "last_seen": "1970-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `runSyncJobHealth` + +Start the targeted health check of a finished, applied sync job again: + +`POST /v1/configs/sync-jobs/{id}/health:run` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | + +**Sample Call** + +```bash +epilot configuration-hub runSyncJobHealth \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub runSyncJobHealth 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub runSyncJobHealth -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + ### `retrySyncJob` Retry the unresolved resources from a prior sync job: `failed` rows, plus @@ -882,7 +1283,7 @@ Retry the unresolved resources from a prior sync job: `failed` rows, plus ```bash epilot configuration-hub retrySyncJob \ -p id=123e4567-e89b-12d3-a456-426614174000 \ - -d '{"payload_overrides":{}}' + -d '{"target_auth_token":"string","resources":[{"type":"string","source_id":"string"}],"payload_overrides":{}}' ``` Using positional args for path parameters: @@ -931,24 +1332,631 @@ epilot configuration-hub retrySyncJob -p id=123e4567-e89b-12d3-a456-426614174000 }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { + "id": "string", + "kind": "resource", "seq": 0, "ts": "1970-01-01T00:00:00.000Z", "phase": "phase_0", "type": "string", "source_id": "string", "target_id": "string", + "title": "string", "status": "pending", + "attempt": 0, "message": "string", - "error": "string" + "error": "string", + "dependency": {} } ], - "errors_sample": [ - { - "type": "string", + "created_by": "string", + "retry_of": "string", + "retried_by": ["string"], + "plan_only": true, + "plan_summary": { + "total": 0, + "create": 0, + "update": 0, + "no_change": 0, + "skip": 0, + "failed": 0, + "not_compared": 0 + }, + "planned_at": "1970-01-01T00:00:00.000Z", + "expires_at": "1970-01-01T00:00:00.000Z", + "applied_from_plan": "string", + "applied_by": ["string"], + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "approvals": [ + { + "job_id": "string", + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z" + } + ], + "errors_sample": [ + { + "type": "string", + "source_id": "string", + "error": "string" + } + ] +} +``` + + + +--- + +### `planSyncJobRetry` + +Preview what `retrySyncJob` would re-run for the same `resources`, + +`POST /v1/configs/sync-jobs/{id}/retry-plan` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | + +**Request Body** + +**Sample Call** + +```bash +epilot configuration-hub planSyncJobRetry \ + -p id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"resources":[{"type":"string","source_id":"string"}]}' +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub planSyncJobRetry 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot configuration-hub planSyncJobRetry -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub planSyncJobRetry -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'selected' +``` + +
+Sample Response + +```json +{ + "selected": [ + { + "type": "string", + "source_id": "string", + "target_id": "string", + "title": "string", + "required_by": { + "type": "string", + "source_id": "string", + "title": "string", + "reason": "typed_reference" + }, + "status": "pending", + "phase": "phase_0", + "attempt": 0, + "error": "string", + "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" + } + ] + }, + "excluded_from_apply": "unchanged" + } + ], + "added_dependencies": [ + { + "type": "string", + "source_id": "string", + "target_id": "string", + "title": "string", + "required_by": { + "type": "string", + "source_id": "string", + "title": "string", + "reason": "typed_reference" + }, + "status": "pending", + "phase": "phase_0", + "attempt": 0, + "error": "string", + "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" + } + ] + }, + "excluded_from_apply": "unchanged" + } + ], + "dependency_resolution": "exact" +} +``` + +
+ +--- + +### `cancelSyncJob` + +Cancel a running sync job. Marks the job `cancelled` with a `finished_at` + +`POST /v1/configs/sync-jobs/{id}/cancel` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | + +**Sample Call** + +```bash +epilot configuration-hub cancelSyncJob \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub cancelSyncJob 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub cancelSyncJob -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "string", + "name": "string", + "status": "pending", + "direction": "push", + "source_org_id": "string", + "target_org_id": "string", + "dry_run": true, + "counts": { + "total": 0, + "pending": 0, + "in_progress": 0, + "succeeded": 0, + "failed": 0, + "skipped_unchanged": 0 + }, + "current_phase": "phase_0", + "current_batch": { + "index": 0, + "of": 0, + "level": 0 + }, + "started_at": "1970-01-01T00:00:00.000Z", + "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, + "events": [ + { + "id": "string", + "kind": "resource", + "seq": 0, + "ts": "1970-01-01T00:00:00.000Z", + "phase": "phase_0", + "type": "string", + "source_id": "string", + "target_id": "string", + "title": "string", + "status": "pending", + "attempt": 0, + "message": "string", + "error": "string", + "dependency": {} + } + ], + "created_by": "string", + "retry_of": "string", + "retried_by": ["string"], + "plan_only": true, + "plan_summary": { + "total": 0, + "create": 0, + "update": 0, + "no_change": 0, + "skip": 0, + "failed": 0, + "not_compared": 0 + }, + "planned_at": "1970-01-01T00:00:00.000Z", + "expires_at": "1970-01-01T00:00:00.000Z", + "applied_from_plan": "string", + "applied_by": ["string"], + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "approvals": [ + { + "job_id": "string", + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z" + } + ], + "errors_sample": [ + { + "type": "string", + "source_id": "string", + "error": "string" + } + ] +} +``` + +
+ +--- + +### `listSyncJobEvents` + +Full activity log of a sync job, latest first: one event per resource + +`GET /v1/configs/sync-jobs/{id}/events` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | +| `cursor` | query | string | No | Opaque cursor for fetching the next page. Omit for the first page. | +| `size` | query | number | No | Number of events per page | +| `after` | query | string | No | Only return events strictly newer than this event id (max 500, +latest first). `truncated: true` means more new events existed than +were returned — reload from the first page. + | +| `kind` | query | string | No | Comma-separated event kinds to return (`resource`, `dependency`, +`step`), e.g. `dependency,step`. Omit for every kind. Applies to +pages, `after` polling and `total`. + | + +**Sample Call** + +```bash +epilot configuration-hub listSyncJobEvents \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub listSyncJobEvents 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub listSyncJobEvents -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "id": "string", + "kind": "resource", + "seq": 0, + "ts": "1970-01-01T00:00:00.000Z", + "phase": "phase_0", + "type": "string", + "source_id": "string", + "target_id": "string", + "title": "string", + "status": "pending", + "attempt": 0, + "message": "string", + "error": "string", + "dependency": { + "outcome": "added", + "reason": "typed_reference", + "required_by": { + "type": "string", + "source_id": "string", + "title": "string" + }, + "depth": 0 + } + } + ], + "next_cursor": "string", + "total": 0, + "truncated": true, + "source": "events" +} +``` + +
+ +--- + +### `listSyncJobResources` + +List the per-resource rows for a sync job. Supports filtering by status + +`GET /v1/configs/sync-jobs/{id}/resources` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | +| `cursor` | query | string | No | Opaque cursor for fetching the next page. Omit for the first page. | +| `size` | query | number | No | Number of items per page | +| `status` | query | "pending" \| "in_progress" \| "created" \| "patched" \| "skipped" \| "would_create" \| "would_patch" \| "would_skip_unchanged" \| "would_match_heuristic" \| "failed" | No | Filter resources by status | +| `type` | query | string | No | Filter resources by resource type | +| `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. + | + +**Sample Call** + +```bash +epilot configuration-hub listSyncJobResources \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub listSyncJobResources 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub listSyncJobResources -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "next_cursor": "string", + "results": [ + { + "type": "string", + "source_id": "string", + "target_id": "string", + "title": "string", + "required_by": { + "type": "string", + "source_id": "string", + "title": "string", + "reason": "typed_reference" + }, + "status": "pending", + "phase": "phase_0", + "attempt": 0, + "error": "string", + "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" + } + ] + }, + "excluded_from_apply": "unchanged" + } + ] +} +``` + +
+ +--- + +### `getSyncPlan` + +Summary of a plan: a dry-run sync job (`createSyncJob` with + +`GET /v1/configs/sync-jobs/{id}/plan` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | + +**Sample Call** + +```bash +epilot configuration-hub getSyncPlan \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub getSyncPlan 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub getSyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'job_id' +``` + +
+Sample Response + +```json +{ + "job_id": "string", + "status": "pending", + "ready": true, + "applicable": true, + "expires_at": "1970-01-01T00:00:00.000Z", + "planned_at": "1970-01-01T00:00:00.000Z", + "replanned_at": "1970-01-01T00:00:00.000Z", + "source_org_id": "string", + "target_org_id": "string", + "started_at": "1970-01-01T00:00:00.000Z", + "finished_at": "1970-01-01T00:00:00.000Z", + "summary": { + "total": 0, + "create": 0, + "update": 0, + "no_change": 0, + "skip": 0, + "failed": 0, + "not_compared": 0 + }, + "by_type": [ + { + "total": 0, + "create": 0, + "update": 0, + "no_change": 0, + "skip": 0, + "failed": 0, + "not_compared": 0, + "type": "string" + } + ], + "applied_by": ["string"] +} +``` + +
+ +--- + +### `listSyncPlanDiffs` + +Field-level diffs of a plan, one per planned resource: what the target + +`GET /v1/configs/sync-jobs/{id}/plan/diffs` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | +| `cursor` | query | string | No | Opaque cursor for fetching the next page. Omit for the first page. | +| `size` | query | number | No | Number of diffs per page | +| `type` | query | string | No | Only diffs of this resource type | +| `action` | query | "create" \| "update" \| "no_change" \| "skip" | No | Only diffs with this plan action | + +**Sample Call** + +```bash +epilot configuration-hub listSyncPlanDiffs \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub listSyncPlanDiffs 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub listSyncPlanDiffs -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "next_cursor": "string", + "results": [ + { + "type": "string", "source_id": "string", - "error": "string" + "target_id": "string", + "title": "string", + "action": "create", + "match": "lineage", + "diff_status": "computed", + "change_count": 0, + "changes": [ + { + "path": "string", + "kind": "added", + "before": {}, + "after": {} + } + ], + "truncated": true, + "note": "string", + "planned_at": "1970-01-01T00:00:00.000Z" } ] } @@ -958,35 +1966,133 @@ epilot configuration-hub retrySyncJob -p id=123e4567-e89b-12d3-a456-426614174000 --- -### `cancelSyncJob` +### `getSyncPlanDiff` -Cancel a running sync job. Marks the job `cancelled` with a `finished_at` +Field-level diff of one planned resource. See `listSyncPlanDiffs`. -`POST /v1/configs/sync-jobs/{id}/cancel` +`GET /v1/configs/sync-jobs/{id}/plan/diffs/{type}/{source_id}` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `id` | path | string | Yes | Sync job ID | +| `type` | path | string | Yes | Resource type | +| `source_id` | path | string | Yes | Source-org resource id | **Sample Call** ```bash -epilot configuration-hub cancelSyncJob \ +epilot configuration-hub getSyncPlanDiff \ + -p id=123e4567-e89b-12d3-a456-426614174000 \ + -p type=example \ + -p source_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub getSyncPlanDiff 123e4567-e89b-12d3-a456-426614174000 example 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub getSyncPlanDiff -p id=123e4567-e89b-12d3-a456-426614174000 -p type=example -p source_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'type' +``` + +
+Sample Response + +```json +{ + "type": "string", + "source_id": "string", + "target_id": "string", + "title": "string", + "action": "create", + "match": "lineage", + "diff_status": "computed", + "change_count": 0, + "changes": [ + { + "path": "string", + "kind": "added", + "before": {}, + "after": {} + } + ], + "truncated": true, + "note": "string", + "planned_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `applySyncPlan` + +Run a finished plan for real. Creates a new sync job (it carries + +`POST /v1/configs/sync-jobs/{id}/plan/apply` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | Sync job ID | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot configuration-hub applySyncPlan \ -p id=123e4567-e89b-12d3-a456-426614174000 ``` +With request body: + +```bash +epilot configuration-hub applySyncPlan \ + -p id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{ + "target_auth_token": "string", + "name": "string", + "resources": [ + { + "type": "string", + "source_id": "string" + } + ], + "exclude": [ + { + "type": "string", + "source_id": "string" + } + ], + "allow_missing_dependencies": false +}' +``` + Using positional args for path parameters: ```bash -epilot configuration-hub cancelSyncJob 123e4567-e89b-12d3-a456-426614174000 +epilot configuration-hub applySyncPlan 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot configuration-hub applySyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 ``` With JSONata filter: ```bash -epilot configuration-hub cancelSyncJob -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' +epilot configuration-hub applySyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' ```
@@ -1017,17 +2123,56 @@ epilot configuration-hub cancelSyncJob -p id=123e4567-e89b-12d3-a456-42661417400 }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { + "id": "string", + "kind": "resource", "seq": 0, "ts": "1970-01-01T00:00:00.000Z", "phase": "phase_0", "type": "string", "source_id": "string", "target_id": "string", + "title": "string", "status": "pending", + "attempt": 0, "message": "string", - "error": "string" + "error": "string", + "dependency": {} + } + ], + "created_by": "string", + "retry_of": "string", + "retried_by": ["string"], + "plan_only": true, + "plan_summary": { + "total": 0, + "create": 0, + "update": 0, + "no_change": 0, + "skip": 0, + "failed": 0, + "not_compared": 0 + }, + "planned_at": "1970-01-01T00:00:00.000Z", + "expires_at": "1970-01-01T00:00:00.000Z", + "applied_from_plan": "string", + "applied_by": ["string"], + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "approvals": [ + { + "job_id": "string", + "approved_by": "string", + "approved_at": "1970-01-01T00:00:00.000Z" } ], "errors_sample": [ @@ -1044,64 +2189,46 @@ epilot configuration-hub cancelSyncJob -p id=123e4567-e89b-12d3-a456-42661417400 --- -### `listSyncJobResources` +### `replanSyncPlan` -List the per-resource rows for a sync job. Supports filtering by status +Plan some rows of a plan again, in place, e.g. after fixing a -`GET /v1/configs/sync-jobs/{id}/resources` +`POST /v1/configs/sync-jobs/{id}/plan/replan` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `id` | path | string | Yes | Sync job ID | -| `cursor` | query | string | No | Opaque cursor for fetching the next page. Omit for the first page. | -| `size` | query | number | No | Number of items per page | -| `status` | query | "pending" \| "in_progress" \| "created" \| "patched" \| "skipped" \| "would_create" \| "would_patch" \| "would_skip_unchanged" \| "would_match_heuristic" \| "failed" | No | Filter resources by status | + +**Request Body** (required) **Sample Call** ```bash -epilot configuration-hub listSyncJobResources \ - -p id=123e4567-e89b-12d3-a456-426614174000 +epilot configuration-hub replanSyncPlan \ + -p id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"target_auth_token":"string","resources":[{"type":"string","source_id":"string"}]}' ``` Using positional args for path parameters: ```bash -epilot configuration-hub listSyncJobResources 123e4567-e89b-12d3-a456-426614174000 +epilot configuration-hub replanSyncPlan 123e4567-e89b-12d3-a456-426614174000 ``` -With JSONata filter: +Using stdin pipe: ```bash -epilot configuration-hub listSyncJobResources -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'results[0]' +cat body.json | epilot configuration-hub replanSyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 ``` -
-Sample Response +With JSONata filter: -```json -{ - "next_cursor": "string", - "results": [ - { - "type": "string", - "source_id": "string", - "target_id": "string", - "title": "string", - "status": "pending", - "phase": "phase_0", - "attempt": 0, - "error": "string", - "updated_at": "1970-01-01T00:00:00.000Z" - } - ] -} +```bash +epilot configuration-hub replanSyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' ``` -
- --- ### `listDeleteJobs` @@ -1418,3 +2545,137 @@ epilot configuration-hub getConfigInventory --jsonata 'inventory_id'
--- + +### `listPreviews` + +Lists the previews requested from the caller's current org, newest first (max 50). + +`GET /v1/previews` + +**Sample Call** + +```bash +epilot configuration-hub listPreviews +``` + +With JSONata filter: + +```bash +epilot configuration-hub listPreviews --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "id": "string", + "name": "string", + "status": "creating_org", + "source_org_id": "string", + "preview_org_id": "string", + "sync_job_id": "string", + "sync_status": "string", + "requested_by": { + "user_id": "string", + "email": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" + } + ] +} +``` + +
+ +--- + +### `createPreview` + +Starts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`, + +`POST /v1/previews` + +**Request Body** + +**Sample Call** + +```bash +epilot configuration-hub createPreview \ + -d '{"name":"string","requester":{"user_id":"string","email":"user@example.com","display_name":"string","language":"de"}}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot configuration-hub createPreview +``` + +With JSONata filter: + +```bash +epilot configuration-hub createPreview --jsonata '$' +``` + +--- + +### `getPreview` + +Returns a preview requested from the caller's current org, with its progress. + +`GET /v1/previews/{id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot configuration-hub getPreview \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot configuration-hub getPreview 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot configuration-hub getPreview -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "string", + "name": "string", + "status": "creating_org", + "source_org_id": "string", + "preview_org_id": "string", + "sync_job_id": "string", + "sync_status": "string", + "requested_by": { + "user_id": "string", + "email": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" +} +``` + +
+ +--- diff --git a/packages/cli/docs/organization.md b/packages/cli/docs/organization.md index de2393fce..c93e0dec5 100644 --- a/packages/cli/docs/organization.md +++ b/packages/cli/docs/organization.md @@ -44,9 +44,14 @@ epilot organization getCurrentOrganization - [`getSettings`](#getsettings) — Retrieves all configuration settings for an organization. - [`putSettingsValue`](#putsettingsvalue) — Creates or updates a specific organization setting identified by its key. - [`deleteSettingsValue`](#deletesettingsvalue) — Removes a specific organization setting identified by its key. +- [`notifySettingsValueUpdated`](#notifysettingsvalueupdated) — Sends an internal notification that an organization setting was updated. **Feature Settings** -- [`getOrganizationFeatureSettings`](#getorganizationfeaturesettings) — Returns feature flag metadata for the organization UI. +- [`getOrganizationFeatureSettings`](#getorganizationfeaturesettings) — Get organization feature settings. + +**Announcements** +- [`getActiveAnnouncement`](#getactiveannouncement) — Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the +- [`acknowledgeAnnouncement`](#acknowledgeannouncement) — Acknowledges an announcement on behalf of the caller's organization. Only org admins ### `getCurrentOrganization` @@ -424,9 +429,64 @@ epilot organization deleteSettingsValue -p org_id=739224 -p key=double_opt_in -- --- +### `notifySettingsValueUpdated` + +Sends an internal notification that an organization setting was updated. + +`POST /v2/organization/{org_id}/settings/{key}/notifications` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `org_id` | path | string | Yes | The unique identifier of the organization | +| `key` | path | string | Yes | The setting key that was updated | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot organization notifySettingsValueUpdated \ + -p org_id=739224 \ + -p key=360_features \ + -d '{"user_email":"string","org_name":"string","value":{"enabled":true}}' +``` + +Using positional args for path parameters: + +```bash +epilot organization notifySettingsValueUpdated 739224 360_features +``` + +Using stdin pipe: + +```bash +cat body.json | epilot organization notifySettingsValueUpdated -p org_id=739224 -p key=360_features +``` + +With JSONata filter: + +```bash +epilot organization notifySettingsValueUpdated -p org_id=739224 -p key=360_features --jsonata 'delivered' +``` + +
+Sample Response + +```json +{ + "delivered": true +} +``` + +
+ +--- + ### `getOrganizationFeatureSettings` -Returns feature flag metadata for the organization UI. +Get organization feature settings. `GET /v2/organization/feature-settings` @@ -443,3 +503,121 @@ epilot organization getOrganizationFeatureSettings --jsonata 'version' ``` --- + +### `getActiveAnnouncement` + +Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the + +`GET /v2/organization/announcements/active` + +**Sample Call** + +```bash +epilot organization getActiveAnnouncement +``` + +With JSONata filter: + +```bash +epilot organization getActiveAnnouncement --jsonata 'announcement' +``` + +
+Sample Response + +```json +{ + "announcement": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "type": "subprocessor_change", + "message": { + "de": "Wir haben unsere Liste der Subdienstleister aktualisiert (gültig ab 01.11.2026). Bitte prüfe die Änderungen.", + "en": "We have updated our list of subprocessors (effective 01.11.2026). Please review the changes." + }, + "link_url": "string", + "list_version": "string", + "effective_date": "1970-01-01", + "window_days": 0, + "status": "draft", + "created_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_at": "1970-01-01T00:00:00.000Z", + "published_at": "1970-01-01T00:00:00.000Z", + "published_by": "string", + "expires_at": "1970-01-01T00:00:00.000Z", + "archived_at": "1970-01-01T00:00:00.000Z", + "archived_by": "string", + "materialized_at": "1970-01-01T00:00:00.000Z" + }, + "can_acknowledge": true +} +``` + +
+ +--- + +### `acknowledgeAnnouncement` + +Acknowledges an announcement on behalf of the caller's organization. Only org admins + +`POST /v2/organization/announcements/{announcement_id}/acknowledge` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `announcement_id` | path | string | Yes | Announcement id | + +**Request Body** + +**Sample Call** + +```bash +epilot organization acknowledgeAnnouncement \ + -p announcement_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"portal":"360"}' +``` + +Using positional args for path parameters: + +```bash +epilot organization acknowledgeAnnouncement 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot organization acknowledgeAnnouncement -p announcement_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot organization acknowledgeAnnouncement -p announcement_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'announcement_id' +``` + +
+Sample Response + +```json +{ + "announcement_id": "string", + "org_id": "string", + "org_name": "string", + "status": "acknowledged", + "acknowledged_by": { + "user_id": "string", + "name": "string", + "email": "string" + }, + "acknowledged_at": "1970-01-01T00:00:00.000Z", + "portal": "360", + "list_version": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- diff --git a/packages/cli/src/generated/api-list.ts b/packages/cli/src/generated/api-list.ts index 975787ccd..80125ca9e 100644 --- a/packages/cli/src/generated/api-list.ts +++ b/packages/cli/src/generated/api-list.ts @@ -319,13 +319,18 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'configuration-hub', title: 'Configuration Hub API', serverUrl: 'https://configuration-hub.sls.epilot.io', - operationCount: 21, + operationCount: 38, operationIds: [ 'listConfigTypes', 'listConfigs', 'getConfigDependencies', 'getConfigUsedBy', 'getIndex', + 'startHealthScan', + 'getHealthReport', + 'requestHealthCoverage', + 'startHealthCleanup', + 'setHealthFindingsIgnored', 'compareConfigs', 'suggestMatches', 'confirmLineage', @@ -333,15 +338,27 @@ export const API_LIST: ApiInfo[] = [ 'listSyncJobs', 'createSyncJob', 'getSyncJob', + 'getSyncJobHealth', + 'runSyncJobHealth', 'retrySyncJob', + 'planSyncJobRetry', 'cancelSyncJob', + 'listSyncJobEvents', 'listSyncJobResources', + 'getSyncPlan', + 'listSyncPlanDiffs', + 'getSyncPlanDiff', + 'applySyncPlan', + 'replanSyncPlan', 'listDeleteJobs', 'createDeleteJob', 'getDeleteJob', 'listDeleteJobResources', 'rebuildIndex', 'getConfigInventory', + 'listPreviews', + 'createPreview', + 'getPreview', ], }, { @@ -1226,7 +1243,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'organization', title: 'Organization API', serverUrl: 'https://organization-v2.sls.epilot.io', - operationCount: 8, + operationCount: 11, operationIds: [ 'getCurrentOrganization', 'getOrganization', @@ -1234,8 +1251,11 @@ export const API_LIST: ApiInfo[] = [ 'getSettings', 'putSettingsValue', 'deleteSettingsValue', + 'notifySettingsValueUpdated', 'getOrganizationFeatureSettings', 'getFeatureSettings', + 'getActiveAnnouncement', + 'acknowledgeAnnouncement', ], }, { diff --git a/packages/epilot-sdk-v2/docs/configuration-hub.md b/packages/epilot-sdk-v2/docs/configuration-hub.md index 5070d8090..fcec05514 100644 --- a/packages/epilot-sdk-v2/docs/configuration-hub.md +++ b/packages/epilot-sdk-v2/docs/configuration-hub.md @@ -50,6 +50,8 @@ const { data } = await configurationHubClient.listConfigTypes(...) - [`createSyncJob`](#createsyncjob) - [`listSyncJobs`](#listsyncjobs) - [`getSyncJob`](#getsyncjob) +- [`getSyncJobHealth`](#getsyncjobhealth) +- [`runSyncJobHealth`](#runsyncjobhealth) - [`retrySyncJob`](#retrysyncjob) - [`planSyncJobRetry`](#plansyncjobretry) - [`cancelSyncJob`](#cancelsyncjob) @@ -67,6 +69,11 @@ const { data } = await configurationHubClient.listConfigTypes(...) - [`getDeleteJob`](#getdeletejob) - [`listDeleteJobResources`](#listdeletejobresources) +**Previews** +- [`createPreview`](#createpreview) +- [`listPreviews`](#listpreviews) +- [`getPreview`](#getpreview) + **Schemas** - [`ResourceType`](#resourcetype) - [`ConfigTypeInfo`](#configtypeinfo) @@ -107,6 +114,10 @@ const { data } = await configurationHubClient.listConfigTypes(...) - [`SyncRequiredBy`](#syncrequiredby) - [`SyncJobEventListResponse`](#syncjobeventlistresponse) - [`SyncJobBatch`](#syncjobbatch) +- [`SyncJobHealthStatus`](#syncjobhealthstatus) +- [`HealthSeverityCounts`](#healthseveritycounts) +- [`SyncJobHealthCheck`](#syncjobhealthcheck) +- [`SyncJobHealth`](#syncjobhealth) - [`SyncJob`](#syncjob) - [`SyncJobListResponse`](#syncjoblistresponse) - [`SyncJobResource`](#syncjobresource) @@ -135,6 +146,9 @@ const { data } = await configurationHubClient.listConfigTypes(...) - [`SnapshotInventoryItem`](#snapshotinventoryitem) - [`SnapshotInventorySkippedType`](#snapshotinventoryskippedtype) - [`ConfigInventoryResponse`](#configinventoryresponse) +- [`CreatePreviewRequest`](#createpreviewrequest) +- [`PreviewStatus`](#previewstatus) +- [`Preview`](#preview) ### `listConfigTypes` @@ -776,6 +790,14 @@ const { data } = await client.createSyncJob( }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { "id": "string", @@ -889,6 +911,14 @@ const { data } = await client.listSyncJobs({ }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { "id": "string", @@ -1000,6 +1030,14 @@ const { data } = await client.getSyncJob({ }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { "id": "string", @@ -1067,6 +1105,88 @@ const { data } = await client.getSyncJob({ --- +### `getSyncJobHealth` + +Targeted health check of an applied (non-dry-run) sync job. When the job +reaches its terminal status, the target org is scanned asynchronously +for the configs the job wrote — rows with status `created + +`GET /v1/configs/sync-jobs/{id}/health` + +```ts +const { data } = await client.getSyncJobHealth({ + id: '123e4567-e89b-12d3-a456-426614174000', +}) +``` + +
+Response + +```json +{ + "status": "pending", + "started_at": "1970-01-01T00:00:00.000Z", + "finished_at": "1970-01-01T00:00:00.000Z", + "scanned": 0, + "counts": { + "error": 0, + "warning": 0, + "info": 0 + }, + "error": "string", + "findings": [ + { + "id": "string", + "code": "broken_reference", + "severity": "error", + "source_type": "string", + "source_id": "string", + "source_title": "string", + "message": "string", + "message_key": "string", + "message_params": {}, + "ref_type": "string", + "ref_id": "string", + "ref_title": "string", + "source_link": "string", + "ref_link": "string", + "owner_type": "string", + "owner_id": "string", + "owner_title": "string", + "owner_link": "string", + "path": "string", + "cleanable": true, + "ignored": true, + "ignored_at": "1970-01-01T00:00:00.000Z", + "ignored_by": "string", + "ignored_reason": "string", + "first_seen": "1970-01-01T00:00:00.000Z", + "last_seen": "1970-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `runSyncJobHealth` + +Start the targeted health check of a finished, applied sync job again: +for jobs that finished before checks existed (`not_run`), checks that +failed or got stuck, or to re-check after fixing something. + +`POST /v1/configs/sync-jobs/{id}/health:run` + +```ts +const { data } = await client.runSyncJobHealth({ + id: '123e4567-e89b-12d3-a456-426614174000', +}) +``` + +--- + ### `retrySyncJob` Retry the unresolved resources from a prior sync job: `failed` rows, plus @@ -1121,6 +1241,14 @@ const { data } = await client.retrySyncJob( }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { "id": "string", @@ -1339,6 +1467,14 @@ const { data } = await client.cancelSyncJob({ }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { "id": "string", @@ -1754,6 +1890,14 @@ const { data } = await client.applySyncPlan( }, "started_at": "1970-01-01T00:00:00.000Z", "finished_at": "1970-01-01T00:00:00.000Z", + "health_check": { + "status": "pending", + "counts": { + "error": 0, + "warning": 0, + "info": 0 + } + }, "events": [ { "id": "string", @@ -2096,6 +2240,109 @@ const { data } = await client.getConfigInventory({ --- +### `createPreview` + +Starts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`, +plus `requester`): a new, temporary org with +`organization_use: Preview` (parent = this org) that receives a + +`POST /v1/previews` + +```ts +const { data } = await client.createPreview( + null, + { + name: 'string', + requester: { + user_id: 'string', + email: 'user@example.com', + display_name: 'string', + language: 'de' + } + }, +) +``` + +--- + +### `listPreviews` + +Lists the previews requested from the caller's current org, newest first (max 50). + +`GET /v1/previews` + +```ts +const { data } = await client.listPreviews() +``` + +
+Response + +```json +{ + "results": [ + { + "id": "string", + "name": "string", + "status": "creating_org", + "source_org_id": "string", + "preview_org_id": "string", + "sync_job_id": "string", + "sync_status": "string", + "requested_by": { + "user_id": "string", + "email": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" + } + ] +} +``` + +
+ +--- + +### `getPreview` + +Returns a preview requested from the caller's current org, with its progress. + +`GET /v1/previews/{id}` + +```ts +const { data } = await client.getPreview({ + id: '123e4567-e89b-12d3-a456-426614174000', +}) +``` + +
+Response + +```json +{ + "id": "string", + "name": "string", + "status": "creating_org", + "source_org_id": "string", + "preview_org_id": "string", + "sync_job_id": "string", + "sync_status": "string", + "requested_by": { + "user_id": "string", + "email": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" +} +``` + +
+ +--- + ## Schemas ### `ResourceType` @@ -2236,7 +2483,7 @@ type HealthScanStartResponse = { ```ts type HealthFinding = { id: string - 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" + 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" severity: "error" | "warning" | "info" source_type: string source_id: string @@ -2290,7 +2537,7 @@ type HealthReportResponse = { error?: string findings: Array<{ id: string - 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" + 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" severity: "error" | "warning" | "info" source_type: string source_id: string @@ -2561,6 +2808,7 @@ type BreakLineageResponse = { ```ts type ErrorResponse = { status: number + code?: string error: string } ``` @@ -2881,6 +3129,95 @@ type SyncJobBatch = { } ``` +### `SyncJobHealthStatus` + +State of a sync job's post-import health check. `pending`: queued, +`running`: scanning, `done`: findings available, `failed`: the check +itself failed or did not finish (the import is unaffected), `skipped`: +nothing to check (a plan, a job that wrote nothing, or a cancelled +job), `not_run`: a finishe + +```ts +type SyncJobHealthStatus = "pending" | "running" | "done" | "failed" | "skipped" | "not_run" +``` + +### `HealthSeverityCounts` + +```ts +type HealthSeverityCounts = { + error: number + warning: number + info: number +} +``` + +### `SyncJobHealthCheck` + +Compact state of the job's post-import health check. Full results: +`getSyncJobHealth`. Absent on plans and on jobs that finished before +the check existed. + + +```ts +type SyncJobHealthCheck = { + status: "pending" | "running" | "done" | "failed" | "skipped" | "not_run" + counts: { + error: number + warning: number + info: number + } +} +``` + +### `SyncJobHealth` + +Targeted health check of the configs a sync job wrote into the target +org. Findings carry `first_seen` = `last_seen` = `finished_at`. + + +```ts +type SyncJobHealth = { + status: "pending" | "running" | "done" | "failed" | "skipped" | "not_run" + started_at?: string // date-time + finished_at?: string // date-time + scanned: number + counts: { + error: number + warning: number + info: number + } + error?: string + findings: Array<{ + id: string + 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" + severity: "error" | "warning" | "info" + source_type: string + source_id: string + source_title: string + message: string + message_key?: string + message_params?: Record + ref_type?: string + ref_id?: string + ref_title?: string + source_link?: string + ref_link?: string + owner_type?: string + owner_id?: string + owner_title?: string + owner_link?: string + path?: string + cleanable?: boolean + ignored?: boolean + ignored_at?: string // date-time + ignored_by?: string + ignored_reason?: string + first_seen: string // date-time + last_seen: string // date-time + }> +} +``` + ### `SyncJob` Sync job header as surfaced by `getSyncJob` and the create response. The @@ -2912,6 +3249,14 @@ type SyncJob = { } started_at: string // date-time finished_at?: string // date-time + health_check?: { + status: "pending" | "running" | "done" | "failed" | "skipped" | "not_run" + counts: { + error: { ... } + warning: { ... } + info: { ... } + } + } events?: Array<{ id?: string kind?: "resource" | "dependency" | "step" @@ -2996,6 +3341,10 @@ type SyncJobListResponse = { } started_at: string // date-time finished_at?: string // date-time + health_check?: { + status: { ... } + counts: { ... } + } events?: Array<{ id?: { ... } kind?: { ... } @@ -3553,3 +3902,51 @@ type ConfigInventoryResponse = { }> } ``` + +### `CreatePreviewRequest` + +```ts +type CreatePreviewRequest = { + name?: string + requester?: { + user_id?: string + email?: string // email + display_name?: string + language?: "de" | "en" + } +} +``` + +### `PreviewStatus` + +- creating_org: the preview org is being created +- copying_configuration: the sync job copying this org's configuration is running +- inviting: the requester is being invited to the preview org +- ready: the requester got the "your preview org is ready" email +- failed: see `error` + + +```ts +type PreviewStatus = "creating_org" | "copying_configuration" | "inviting" | "ready" | "failed" +``` + +### `Preview` + +```ts +type Preview = { + id: string + name: string + status: "creating_org" | "copying_configuration" | "inviting" | "ready" | "failed" + source_org_id: string + preview_org_id?: string + sync_job_id?: string + sync_status?: string + requested_by: { + user_id?: string + email?: string + } + created_at: string // date-time + updated_at: string // date-time + error?: string +} +``` diff --git a/packages/epilot-sdk-v2/docs/organization.md b/packages/epilot-sdk-v2/docs/organization.md index 9a6297414..d5e3ff5bb 100644 --- a/packages/epilot-sdk-v2/docs/organization.md +++ b/packages/epilot-sdk-v2/docs/organization.md @@ -33,29 +33,30 @@ const { data } = await organizationClient.getCurrentOrganization(...) - [`getSettings`](#getsettings) - [`putSettingsValue`](#putsettingsvalue) - [`deleteSettingsValue`](#deletesettingsvalue) +- [`notifySettingsValueUpdated`](#notifysettingsvalueupdated) **Feature Settings** - [`getOrganizationFeatureSettings`](#getorganizationfeaturesettings) +**Announcements** +- [`getActiveAnnouncement`](#getactiveannouncement) +- [`acknowledgeAnnouncement`](#acknowledgeannouncement) + **Schemas** - [`OrganizationId`](#organizationid) - [`Organization`](#organization) -- [`InternalOrganization`](#internalorganization) -- [`SettingKey`](#settingkey) - [`Settings`](#settings) - [`SettingsValue`](#settingsvalue) -- [`CreateOrganizationRequest`](#createorganizationrequest) -- [`DataPointsResponse`](#datapointsresponse) -- [`DataPoint`](#datapoint) -- [`OrganizationToCleanup`](#organizationtocleanup) -- [`OrganizationCleanupStatus`](#organizationcleanupstatus) -- [`HubspotCompany`](#hubspotcompany) -- [`HubspotCompaniesResponse`](#hubspotcompaniesresponse) -- [`HubspotOrganizationData`](#hubspotorganizationdata) - [`FeatureSettings`](#featuresettings) - [`FeatureFlagMetadata`](#featureflagmetadata) - [`I18nString`](#i18nstring) - [`VisibilityRule`](#visibilityrule) +- [`AnnouncementType`](#announcementtype) +- [`AnnouncementMessage`](#announcementmessage) +- [`Announcement`](#announcement) +- [`AnnouncementOrganizationStatusValue`](#announcementorganizationstatusvalue) +- [`AnnouncementOrganizationStatus`](#announcementorganizationstatus) +- [`ActiveAnnouncementResponse`](#activeannouncementresponse) ### `getCurrentOrganization` @@ -309,9 +310,44 @@ const { data } = await client.deleteSettingsValue({ --- +### `notifySettingsValueUpdated` + +Sends an internal notification that an organization setting was updated. + +`POST /v2/organization/{org_id}/settings/{key}/notifications` + +```ts +const { data } = await client.notifySettingsValueUpdated( + { + org_id: 'example', + key: 'example', + }, + { + user_email: 'string', + org_name: 'string', + value: { + enabled: true + } + }, +) +``` + +
+Response + +```json +{ + "delivered": true +} +``` + +
+ +--- + ### `getOrganizationFeatureSettings` -Get organization feature settings +Get organization feature settings. `GET /v2/organization/feature-settings` @@ -321,6 +357,95 @@ const { data } = await client.getOrganizationFeatureSettings() --- +### `getActiveAnnouncement` + +Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the +caller's organization. + +`GET /v2/organization/announcements/active` + +```ts +const { data } = await client.getActiveAnnouncement() +``` + +
+Response + +```json +{ + "announcement": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "type": "subprocessor_change", + "message": { + "de": "Wir haben unsere Liste der Subdienstleister aktualisiert (gültig ab 01.11.2026). Bitte prüfe die Änderungen.", + "en": "We have updated our list of subprocessors (effective 01.11.2026). Please review the changes." + }, + "link_url": "string", + "list_version": "string", + "effective_date": "1970-01-01", + "window_days": 0, + "status": "draft", + "created_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_at": "1970-01-01T00:00:00.000Z", + "published_at": "1970-01-01T00:00:00.000Z", + "published_by": "string", + "expires_at": "1970-01-01T00:00:00.000Z", + "archived_at": "1970-01-01T00:00:00.000Z", + "archived_by": "string", + "materialized_at": "1970-01-01T00:00:00.000Z" + }, + "can_acknowledge": true +} +``` + +
+ +--- + +### `acknowledgeAnnouncement` + +Acknowledges an announcement on behalf of the caller's organization. Only org admins +(`organization:edit`) may acknowledge. + +`POST /v2/organization/announcements/{announcement_id}/acknowledge` + +```ts +const { data } = await client.acknowledgeAnnouncement( + { + announcement_id: 'example', + }, + { + portal: '360' + }, +) +``` + +
+Response + +```json +{ + "announcement_id": "string", + "org_id": "string", + "org_name": "string", + "status": "acknowledged", + "acknowledged_by": { + "user_id": "string", + "name": "string", + "email": "string" + }, + "acknowledged_at": "1970-01-01T00:00:00.000Z", + "portal": "360", + "list_version": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + ## Schemas ### `OrganizationId` @@ -353,45 +478,7 @@ type Organization = { street?: string street_number?: string } - organization_use?: "Production" | "Sandbox" - parent_production_org_id?: string - created_date?: string // date-time - logo_url?: string - logo_thumbnail_url?: string - signature?: string - is_unlicensed_org?: boolean - style?: Record - type?: "Vendor" | "Partner" - symbol?: string - pricing_tier?: string - free_user_limit?: number - tags?: string[] -} -``` - -### `InternalOrganization` - -Extended organization object with internal-only fields. - -Includes all fields from the base Organization schema plus additional -internal metadata used for platform administration and support. - - -```ts -type InternalOrganization = { - id?: string - name?: string - email?: string - phone?: string - website?: string - address?: { - country?: string - city?: string - postal_code?: string - street?: string - street_number?: string - } - organization_use?: "Production" | "Sandbox" + organization_use?: "Production" | "Sandbox" | "Preview" parent_production_org_id?: string created_date?: string // date-time logo_url?: string @@ -407,16 +494,6 @@ type InternalOrganization = { } ``` -### `SettingKey` - -A unique key identifying an organization setting. -Common setting keys include: double_opt_in, email_tracking, default_language, workflow_notifications - - -```ts -type SettingKey = string -``` - ### `Settings` A key-value map of all organization settings. @@ -437,141 +514,6 @@ Can be any valid JSON type: string, number, boolean, array, or object. type SettingsValue = string | number | boolean | Record[] | Record ``` -### `CreateOrganizationRequest` - -Request payload for creating a new organization - -```ts -type CreateOrganizationRequest = { - organization_detail?: { - name: string - email_address?: string - type: string - organization_use?: "Production" | "Sandbox" - pricing_tier_id: string - } - owner_user?: { - full_name?: string - email_address: string - } -} -``` - -### `DataPointsResponse` - -List of data point metrics for all organizations - -```ts -type DataPointsResponse = Array<{ - id?: number - max_customer?: number - actual_customer?: number -}> -``` - -### `DataPoint` - -Data point metrics for a single organization, used for usage tracking and billing - -```ts -type DataPoint = { - id?: number - max_customer?: number - actual_customer?: number -} -``` - -### `OrganizationToCleanup` - -Represents an organization that has been marked for deletion and requires cleanup. -Contains metadata about the deletion request. - - -```ts -type OrganizationToCleanup = { - org_id: string - deleted_at: string // date-time - deleted_by: string -} -``` - -### `OrganizationCleanupStatus` - -Records the cleanup status reported by a specific service for an organization. -Each service that stores organization data reports its cleanup operations here. - - -```ts -type OrganizationCleanupStatus = { - org_id: string - service_name: string - operations?: Array<{ - action?: string - resource?: string - extra_info?: string | number | boolean | Record[] | Record - }> -} -``` - -### `HubspotCompany` - -Represents a company record from HubSpot CRM synchronized to the epilot data warehouse. -Used for CRM integration and organization mapping. - - -```ts -type HubspotCompany = { - company_id?: string - company_name?: string - domain?: string -} -``` - -### `HubspotCompaniesResponse` - -Response containing a list of HubSpot companies matching the search criteria - -```ts -type HubspotCompaniesResponse = { - results?: Array<{ - company_id?: string - company_name?: string - domain?: string - }> - total?: number -} -``` - -### `HubspotOrganizationData` - -HubSpot company data associated with an epilot organization. -Contains business metrics and CRM properties synced from HubSpot. - - -```ts -type HubspotOrganizationData = { - company_id?: string - company_name?: string - domain?: string - current_mrr?: string - potential_mrr?: string - company_size?: string - pricing_tier?: string - lifecyclestage?: string - industry?: string - numberofemployees?: string - customer_number?: string - no_of_users?: string - activation_rate?: string - active_features?: string - usecases_implemented?: string - buyer_journey?: string - country?: string - city?: string - properties?: Record -} -``` - ### `FeatureSettings` Feature settings metadata served to frontend applications. @@ -598,6 +540,7 @@ type FeatureSettings = { type: { ... } setting_key?: { ... } flag_name?: { ... } + pricing_tier_classes?: { ... } action?: { ... } resource?: { ... } rule?: { ... } @@ -628,9 +571,10 @@ type FeatureFlagMetadata = { reverse_checked?: boolean is_visible_for_partner?: boolean visibility_rules: Array<{ - type: "always" | "never" | "advanced_mode" | "pricing_tier" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or" + type: "always" | "never" | "advanced_mode" | "pricing_tier" | "pricing_tier_class" | "production_org" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or" setting_key?: string flag_name?: string + pricing_tier_classes?: string[] action?: string resource?: string rule?: { ... } @@ -658,12 +602,111 @@ at the top level. Supports boolean combinators (and, or, not) for complex logic. ```ts type VisibilityRule = { - type: "always" | "never" | "advanced_mode" | "pricing_tier" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or" + type: "always" | "never" | "advanced_mode" | "pricing_tier" | "pricing_tier_class" | "production_org" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or" setting_key?: string flag_name?: string + pricing_tier_classes?: string[] action?: string resource?: string rule?: { ... } rules?: { ... }[] } ``` + +### `AnnouncementType` + +```ts +type AnnouncementType = "subprocessor_change" +``` + +### `AnnouncementMessage` + +```ts +type AnnouncementMessage = { + de: string + en?: string +} +``` + +### `Announcement` + +```ts +type Announcement = { + id: string // uuid + type: "subprocessor_change" + message: { + de: string + en?: string + } + link_url: string + list_version: string + effective_date: string // date + window_days: number + status: "draft" | "published" | "archived" + created_at: string // date-time + created_by?: string + updated_at: string // date-time + published_at?: string // date-time + published_by?: string + expires_at?: string // date-time + archived_at?: string // date-time + archived_by?: string + materialized_at?: string // date-time +} +``` + +### `AnnouncementOrganizationStatusValue` + +```ts +type AnnouncementOrganizationStatusValue = "acknowledged" | "pending" | "expired" | "superseded" +``` + +### `AnnouncementOrganizationStatus` + +```ts +type AnnouncementOrganizationStatus = { + announcement_id: string + org_id: string + org_name?: string + status: "acknowledged" | "pending" | "expired" | "superseded" + acknowledged_by?: { + user_id: string + name?: string + email?: string + } + acknowledged_at?: string // date-time + portal?: string + list_version: string + recorded_at?: string // date-time +} +``` + +### `ActiveAnnouncementResponse` + +```ts +type ActiveAnnouncementResponse = { + announcement: { + id: string // uuid + type: "subprocessor_change" + message: { + de: { ... } + en?: { ... } + } + link_url: string + list_version: string + effective_date: string // date + window_days: number + status: "draft" | "published" | "archived" + created_at: string // date-time + created_by?: string + updated_at: string // date-time + published_at?: string // date-time + published_by?: string + expires_at?: string // date-time + archived_at?: string // date-time + archived_by?: string + materialized_at?: string // date-time + } + can_acknowledge: boolean +} +``` 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 ea2bc5a8f..eebeb2414 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"]]],["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"]]]],"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"]]],["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/definitions/organization-runtime.json b/packages/epilot-sdk-v2/src/definitions/organization-runtime.json index 9495f1da5..8936cc68e 100644 --- a/packages/epilot-sdk-v2/src/definitions/organization-runtime.json +++ b/packages/epilot-sdk-v2/src/definitions/organization-runtime.json @@ -1 +1 @@ -{"s":"https://organization-v2.sls.epilot.io","o":[["getCurrentOrganization","get","/v2/organization/current"],["getOrganization","get","/v2/organization/{org_id}",[["org_id","p",true]]],["updateOrganization","patch","/v2/organization/{org_id}",[["org_id","p",true]],1],["getSettings","get","/v2/organization/{org_id}/settings",[["org_id","p",true]]],["putSettingsValue","put","/v2/organization/{org_id}/settings/{key}",[["org_id","p",true],["key","p",true]],1],["deleteSettingsValue","delete","/v2/organization/{org_id}/settings/{key}",[["org_id","p",true],["key","p",true]]],["getOrganizationFeatureSettings","get","/v2/organization/feature-settings"],["getFeatureSettings","get","/v2/feature-settings"]],"v":"3.0.3"} \ No newline at end of file +{"s":"https://organization-v2.sls.epilot.io","o":[["getCurrentOrganization","get","/v2/organization/current"],["getOrganization","get","/v2/organization/{org_id}",[["org_id","p",true]]],["updateOrganization","patch","/v2/organization/{org_id}",[["org_id","p",true]],1],["getSettings","get","/v2/organization/{org_id}/settings",[["org_id","p",true]]],["putSettingsValue","put","/v2/organization/{org_id}/settings/{key}",[["org_id","p",true],["key","p",true]],1],["deleteSettingsValue","delete","/v2/organization/{org_id}/settings/{key}",[["org_id","p",true],["key","p",true]]],["notifySettingsValueUpdated","post","/v2/organization/{org_id}/settings/{key}/notifications",[["org_id","p",true],["key","p",true]],1],["getOrganizationFeatureSettings","get","/v2/organization/feature-settings"],["getFeatureSettings","get","/v2/feature-settings"],["getActiveAnnouncement","get","/v2/organization/announcements/active"],["acknowledgeAnnouncement","post","/v2/organization/announcements/{announcement_id}/acknowledge",[["AnnouncementIdPathParam"]],1]],"v":"3.0.3","cp":{"AnnouncementIdPathParam":["announcement_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 2c9411fb8..96cd2f60a 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- [`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**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- [`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\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 \"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 \"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 \"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### `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 \"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 \"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 \"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## 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\"\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\"\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 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### `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 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 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" +"# 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" diff --git a/packages/epilot-sdk-v2/src/docs/organization.json b/packages/epilot-sdk-v2/src/docs/organization.json index 67f711c39..e6fec5951 100644 --- a/packages/epilot-sdk-v2/src/docs/organization.json +++ b/packages/epilot-sdk-v2/src/docs/organization.json @@ -1 +1 @@ -"# Organization API\n\n- **Base URL:** `https://organization-v2.sls.epilot.io`\n- **Full API Docs:** [https://docs.epilot.io/api/organization](https://docs.epilot.io/api/organization)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.organization.getCurrentOrganization(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/organization'\n\nconst organizationClient = getClient()\nauthorize(organizationClient, () => '')\nconst { data } = await organizationClient.getCurrentOrganization(...)\n```\n\n## Operations\n\n**Organization**\n- [`getCurrentOrganization`](#getcurrentorganization)\n- [`getOrganization`](#getorganization)\n- [`updateOrganization`](#updateorganization)\n\n**Organization Settings**\n- [`getSettings`](#getsettings)\n- [`putSettingsValue`](#putsettingsvalue)\n- [`deleteSettingsValue`](#deletesettingsvalue)\n\n**Feature Settings**\n- [`getOrganizationFeatureSettings`](#getorganizationfeaturesettings)\n\n**Schemas**\n- [`OrganizationId`](#organizationid)\n- [`Organization`](#organization)\n- [`InternalOrganization`](#internalorganization)\n- [`SettingKey`](#settingkey)\n- [`Settings`](#settings)\n- [`SettingsValue`](#settingsvalue)\n- [`CreateOrganizationRequest`](#createorganizationrequest)\n- [`DataPointsResponse`](#datapointsresponse)\n- [`DataPoint`](#datapoint)\n- [`OrganizationToCleanup`](#organizationtocleanup)\n- [`OrganizationCleanupStatus`](#organizationcleanupstatus)\n- [`HubspotCompany`](#hubspotcompany)\n- [`HubspotCompaniesResponse`](#hubspotcompaniesresponse)\n- [`HubspotOrganizationData`](#hubspotorganizationdata)\n- [`FeatureSettings`](#featuresettings)\n- [`FeatureFlagMetadata`](#featureflagmetadata)\n- [`I18nString`](#i18nstring)\n- [`VisibilityRule`](#visibilityrule)\n\n### `getCurrentOrganization`\n\nRetrieves the organization associated with the authenticated user's current session.\n\n`GET /v2/organization/current`\n\n```ts\nconst { data } = await client.getCurrentOrganization()\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"739224\",\n \"name\": \"Epilot\",\n \"email\": \"someone@epilot.cloud\",\n \"phone\": \"+49123123123\",\n \"website\": \"https://epilot.cloud\",\n \"address\": {\n \"country\": \"string\",\n \"city\": \"string\",\n \"postal_code\": \"string\",\n \"street\": \"string\",\n \"street_number\": \"string\"\n },\n \"organization_use\": \"Production\",\n \"parent_production_org_id\": \"string\",\n \"created_date\": \"1970-01-01T00:00:00.000Z\",\n \"logo_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"logo_thumbnail_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"signature\": \"

Thanks

\",\n \"is_unlicensed_org\": false,\n \"style\": {},\n \"type\": \"Vendor\",\n \"symbol\": \"EPI\",\n \"pricing_tier\": \"professional\",\n \"free_user_limit\": 50,\n \"tags\": [\"test org\"]\n}\n```\n\n
\n\n---\n\n### `getOrganization`\n\nRetrieves detailed information about a specific organization by its unique identifier.\n\n`GET /v2/organization/{org_id}`\n\n```ts\nconst { data } = await client.getOrganization({\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"739224\",\n \"name\": \"Epilot\",\n \"email\": \"someone@epilot.cloud\",\n \"phone\": \"+49123123123\",\n \"website\": \"https://epilot.cloud\",\n \"address\": {\n \"country\": \"string\",\n \"city\": \"string\",\n \"postal_code\": \"string\",\n \"street\": \"string\",\n \"street_number\": \"string\"\n },\n \"organization_use\": \"Production\",\n \"parent_production_org_id\": \"string\",\n \"created_date\": \"1970-01-01T00:00:00.000Z\",\n \"logo_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"logo_thumbnail_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"signature\": \"

Thanks

\",\n \"is_unlicensed_org\": false,\n \"style\": {},\n \"type\": \"Vendor\",\n \"symbol\": \"EPI\",\n \"pricing_tier\": \"professional\",\n \"free_user_limit\": 50,\n \"tags\": [\"test org\"]\n}\n```\n\n
\n\n---\n\n### `updateOrganization`\n\nUpdates an organization's profile information.\n\n`PATCH /v2/organization/{org_id}`\n\n```ts\nconst { data } = await client.updateOrganization(\n {\n org_id: 'example',\n },\n {\n id: '739224',\n name: 'Epilot',\n email: 'someone@epilot.cloud',\n phone: '+49123123123',\n website: 'https://epilot.cloud',\n address: {\n country: 'string',\n city: 'string',\n postal_code: 'string',\n street: 'string',\n street_number: 'string'\n },\n organization_use: 'Production',\n parent_production_org_id: 'string',\n created_date: '1970-01-01T00:00:00.000Z',\n logo_url: 'https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png',\n logo_thumbnail_url: 'https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png',\n signature: '

Thanks

',\n is_unlicensed_org: false,\n style: {},\n type: 'Vendor',\n symbol: 'EPI',\n pricing_tier: 'professional',\n free_user_limit: 50,\n tags: ['test org']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"739224\",\n \"name\": \"Epilot\",\n \"email\": \"someone@epilot.cloud\",\n \"phone\": \"+49123123123\",\n \"website\": \"https://epilot.cloud\",\n \"address\": {\n \"country\": \"string\",\n \"city\": \"string\",\n \"postal_code\": \"string\",\n \"street\": \"string\",\n \"street_number\": \"string\"\n },\n \"organization_use\": \"Production\",\n \"parent_production_org_id\": \"string\",\n \"created_date\": \"1970-01-01T00:00:00.000Z\",\n \"logo_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"logo_thumbnail_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"signature\": \"

Thanks

\",\n \"is_unlicensed_org\": false,\n \"style\": {},\n \"type\": \"Vendor\",\n \"symbol\": \"EPI\",\n \"pricing_tier\": \"professional\",\n \"free_user_limit\": 50,\n \"tags\": [\"test org\"]\n}\n```\n\n
\n\n---\n\n### `getSettings`\n\nRetrieves all configuration settings for an organization.\n\n`GET /v2/organization/{org_id}/settings`\n\n```ts\nconst { data } = await client.getSettings({\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"double_opt_in\": {\n \"enabled\": true\n },\n \"email_tracking\": {\n \"enabled\": true,\n \"track_opens\": true\n },\n \"default_language\": \"de\"\n}\n```\n\n
\n\n---\n\n### `putSettingsValue`\n\nCreates or updates a specific organization setting identified by its key.\n\n`PUT /v2/organization/{org_id}/settings/{key}`\n\n```ts\nconst { data } = await client.putSettingsValue(\n {\n org_id: 'example',\n key: 'example',\n },\n {\n enabled: true\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"enabled\": true\n}\n```\n\n
\n\n---\n\n### `deleteSettingsValue`\n\nRemoves a specific organization setting identified by its key.\n\n`DELETE /v2/organization/{org_id}/settings/{key}`\n\n```ts\nconst { data } = await client.deleteSettingsValue({\n org_id: 'example',\n key: 'example',\n})\n```\n\n---\n\n### `getOrganizationFeatureSettings`\n\nGet organization feature settings\n\n`GET /v2/organization/feature-settings`\n\n```ts\nconst { data } = await client.getOrganizationFeatureSettings()\n```\n\n---\n\n## Schemas\n\n### `OrganizationId`\n\nUnique identifier for an organization (tenant) in the epilot platform\n\n```ts\ntype OrganizationId = string\n```\n\n### `Organization`\n\nRepresents an epilot organization (tenant).\n\nAn organization contains all the configuration, branding, and contact information\nfor a tenant account on the epilot platform.\n\n\n```ts\ntype Organization = {\n id?: string\n name?: string\n email?: string\n phone?: string\n website?: string\n address?: {\n country?: string\n city?: string\n postal_code?: string\n street?: string\n street_number?: string\n }\n organization_use?: \"Production\" | \"Sandbox\"\n parent_production_org_id?: string\n created_date?: string // date-time\n logo_url?: string\n logo_thumbnail_url?: string\n signature?: string\n is_unlicensed_org?: boolean\n style?: Record\n type?: \"Vendor\" | \"Partner\"\n symbol?: string\n pricing_tier?: string\n free_user_limit?: number\n tags?: string[]\n}\n```\n\n### `InternalOrganization`\n\nExtended organization object with internal-only fields.\n\nIncludes all fields from the base Organization schema plus additional\ninternal metadata used for platform administration and support.\n\n\n```ts\ntype InternalOrganization = {\n id?: string\n name?: string\n email?: string\n phone?: string\n website?: string\n address?: {\n country?: string\n city?: string\n postal_code?: string\n street?: string\n street_number?: string\n }\n organization_use?: \"Production\" | \"Sandbox\"\n parent_production_org_id?: string\n created_date?: string // date-time\n logo_url?: string\n logo_thumbnail_url?: string\n signature?: string\n is_unlicensed_org?: boolean\n style?: Record\n type?: \"Vendor\" | \"Partner\"\n symbol?: string\n pricing_tier?: string\n free_user_limit?: number\n tags?: string[]\n}\n```\n\n### `SettingKey`\n\nA unique key identifying an organization setting.\nCommon setting keys include: double_opt_in, email_tracking, default_language, workflow_notifications\n\n\n```ts\ntype SettingKey = string\n```\n\n### `Settings`\n\nA key-value map of all organization settings.\nKeys are setting identifiers and values can be any valid JSON type.\n\n\n```ts\ntype Settings = Record\n```\n\n### `SettingsValue`\n\nThe value of an organization setting.\nCan be any valid JSON type: string, number, boolean, array, or object.\n\n\n```ts\ntype SettingsValue = string | number | boolean | Record[] | Record\n```\n\n### `CreateOrganizationRequest`\n\nRequest payload for creating a new organization\n\n```ts\ntype CreateOrganizationRequest = {\n organization_detail?: {\n name: string\n email_address?: string\n type: string\n organization_use?: \"Production\" | \"Sandbox\"\n pricing_tier_id: string\n }\n owner_user?: {\n full_name?: string\n email_address: string\n }\n}\n```\n\n### `DataPointsResponse`\n\nList of data point metrics for all organizations\n\n```ts\ntype DataPointsResponse = Array<{\n id?: number\n max_customer?: number\n actual_customer?: number\n}>\n```\n\n### `DataPoint`\n\nData point metrics for a single organization, used for usage tracking and billing\n\n```ts\ntype DataPoint = {\n id?: number\n max_customer?: number\n actual_customer?: number\n}\n```\n\n### `OrganizationToCleanup`\n\nRepresents an organization that has been marked for deletion and requires cleanup.\nContains metadata about the deletion request.\n\n\n```ts\ntype OrganizationToCleanup = {\n org_id: string\n deleted_at: string // date-time\n deleted_by: string\n}\n```\n\n### `OrganizationCleanupStatus`\n\nRecords the cleanup status reported by a specific service for an organization.\nEach service that stores organization data reports its cleanup operations here.\n\n\n```ts\ntype OrganizationCleanupStatus = {\n org_id: string\n service_name: string\n operations?: Array<{\n action?: string\n resource?: string\n extra_info?: string | number | boolean | Record[] | Record\n }>\n}\n```\n\n### `HubspotCompany`\n\nRepresents a company record from HubSpot CRM synchronized to the epilot data warehouse.\nUsed for CRM integration and organization mapping.\n\n\n```ts\ntype HubspotCompany = {\n company_id?: string\n company_name?: string\n domain?: string\n}\n```\n\n### `HubspotCompaniesResponse`\n\nResponse containing a list of HubSpot companies matching the search criteria\n\n```ts\ntype HubspotCompaniesResponse = {\n results?: Array<{\n company_id?: string\n company_name?: string\n domain?: string\n }>\n total?: number\n}\n```\n\n### `HubspotOrganizationData`\n\nHubSpot company data associated with an epilot organization.\nContains business metrics and CRM properties synced from HubSpot.\n\n\n```ts\ntype HubspotOrganizationData = {\n company_id?: string\n company_name?: string\n domain?: string\n current_mrr?: string\n potential_mrr?: string\n company_size?: string\n pricing_tier?: string\n lifecyclestage?: string\n industry?: string\n numberofemployees?: string\n customer_number?: string\n no_of_users?: string\n activation_rate?: string\n active_features?: string\n usecases_implemented?: string\n buyer_journey?: string\n country?: string\n city?: string\n properties?: Record\n}\n```\n\n### `FeatureSettings`\n\nFeature settings metadata served to frontend applications.\n\n```ts\ntype FeatureSettings = {\n version: string\n feature_flags: Array<{\n feature_name: string\n title?: {\n key: { ... }\n default_value?: { ... }\n }\n description?: {\n key: { ... }\n default_value?: { ... }\n }\n badge?: \"alpha\" | \"beta\" | \"new\" | \"advanced\" | \"experiment\" | \"deprecated\"\n confetti?: boolean\n one_way?: boolean\n reverse_checked?: boolean\n is_visible_for_partner?: boolean\n visibility_rules: Array<{\n type: { ... }\n setting_key?: { ... }\n flag_name?: { ... }\n action?: { ... }\n resource?: { ... }\n rule?: { ... }\n rules?: { ... }\n }>\n }>\n}\n```\n\n### `FeatureFlagMetadata`\n\nMetadata for a single feature flag toggle\n\n```ts\ntype FeatureFlagMetadata = {\n feature_name: string\n title?: {\n key: string\n default_value?: string\n }\n description?: {\n key: string\n default_value?: string\n }\n badge?: \"alpha\" | \"beta\" | \"new\" | \"advanced\" | \"experiment\" | \"deprecated\"\n confetti?: boolean\n one_way?: boolean\n reverse_checked?: boolean\n is_visible_for_partner?: boolean\n visibility_rules: Array<{\n type: \"always\" | \"never\" | \"advanced_mode\" | \"pricing_tier\" | \"feature_flag\" | \"permission\" | \"setting_enabled\" | \"flag_enabled\" | \"not\" | \"and\" | \"or\"\n setting_key?: string\n flag_name?: string\n action?: string\n resource?: string\n rule?: { ... }\n rules?: { ... }[]\n }>\n}\n```\n\n### `I18nString`\n\nInternationalized string with a translation key and optional default value\n\n```ts\ntype I18nString = {\n key: string\n default_value?: string\n}\n```\n\n### `VisibilityRule`\n\nA rule that determines feature visibility. Rules are combined with AND logic\nat the top level. Supports boolean combinators (and, or, not) for complex logic.\n\n\n```ts\ntype VisibilityRule = {\n type: \"always\" | \"never\" | \"advanced_mode\" | \"pricing_tier\" | \"feature_flag\" | \"permission\" | \"setting_enabled\" | \"flag_enabled\" | \"not\" | \"and\" | \"or\"\n setting_key?: string\n flag_name?: string\n action?: string\n resource?: string\n rule?: { ... }\n rules?: { ... }[]\n}\n```\n" +"# Organization API\n\n- **Base URL:** `https://organization-v2.sls.epilot.io`\n- **Full API Docs:** [https://docs.epilot.io/api/organization](https://docs.epilot.io/api/organization)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.organization.getCurrentOrganization(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/organization'\n\nconst organizationClient = getClient()\nauthorize(organizationClient, () => '')\nconst { data } = await organizationClient.getCurrentOrganization(...)\n```\n\n## Operations\n\n**Organization**\n- [`getCurrentOrganization`](#getcurrentorganization)\n- [`getOrganization`](#getorganization)\n- [`updateOrganization`](#updateorganization)\n\n**Organization Settings**\n- [`getSettings`](#getsettings)\n- [`putSettingsValue`](#putsettingsvalue)\n- [`deleteSettingsValue`](#deletesettingsvalue)\n- [`notifySettingsValueUpdated`](#notifysettingsvalueupdated)\n\n**Feature Settings**\n- [`getOrganizationFeatureSettings`](#getorganizationfeaturesettings)\n\n**Announcements**\n- [`getActiveAnnouncement`](#getactiveannouncement)\n- [`acknowledgeAnnouncement`](#acknowledgeannouncement)\n\n**Schemas**\n- [`OrganizationId`](#organizationid)\n- [`Organization`](#organization)\n- [`Settings`](#settings)\n- [`SettingsValue`](#settingsvalue)\n- [`FeatureSettings`](#featuresettings)\n- [`FeatureFlagMetadata`](#featureflagmetadata)\n- [`I18nString`](#i18nstring)\n- [`VisibilityRule`](#visibilityrule)\n- [`AnnouncementType`](#announcementtype)\n- [`AnnouncementMessage`](#announcementmessage)\n- [`Announcement`](#announcement)\n- [`AnnouncementOrganizationStatusValue`](#announcementorganizationstatusvalue)\n- [`AnnouncementOrganizationStatus`](#announcementorganizationstatus)\n- [`ActiveAnnouncementResponse`](#activeannouncementresponse)\n\n### `getCurrentOrganization`\n\nRetrieves the organization associated with the authenticated user's current session.\n\n`GET /v2/organization/current`\n\n```ts\nconst { data } = await client.getCurrentOrganization()\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"739224\",\n \"name\": \"Epilot\",\n \"email\": \"someone@epilot.cloud\",\n \"phone\": \"+49123123123\",\n \"website\": \"https://epilot.cloud\",\n \"address\": {\n \"country\": \"string\",\n \"city\": \"string\",\n \"postal_code\": \"string\",\n \"street\": \"string\",\n \"street_number\": \"string\"\n },\n \"organization_use\": \"Production\",\n \"parent_production_org_id\": \"string\",\n \"created_date\": \"1970-01-01T00:00:00.000Z\",\n \"logo_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"logo_thumbnail_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"signature\": \"

Thanks

\",\n \"is_unlicensed_org\": false,\n \"style\": {},\n \"type\": \"Vendor\",\n \"symbol\": \"EPI\",\n \"pricing_tier\": \"professional\",\n \"free_user_limit\": 50,\n \"tags\": [\"test org\"]\n}\n```\n\n
\n\n---\n\n### `getOrganization`\n\nRetrieves detailed information about a specific organization by its unique identifier.\n\n`GET /v2/organization/{org_id}`\n\n```ts\nconst { data } = await client.getOrganization({\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"739224\",\n \"name\": \"Epilot\",\n \"email\": \"someone@epilot.cloud\",\n \"phone\": \"+49123123123\",\n \"website\": \"https://epilot.cloud\",\n \"address\": {\n \"country\": \"string\",\n \"city\": \"string\",\n \"postal_code\": \"string\",\n \"street\": \"string\",\n \"street_number\": \"string\"\n },\n \"organization_use\": \"Production\",\n \"parent_production_org_id\": \"string\",\n \"created_date\": \"1970-01-01T00:00:00.000Z\",\n \"logo_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"logo_thumbnail_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"signature\": \"

Thanks

\",\n \"is_unlicensed_org\": false,\n \"style\": {},\n \"type\": \"Vendor\",\n \"symbol\": \"EPI\",\n \"pricing_tier\": \"professional\",\n \"free_user_limit\": 50,\n \"tags\": [\"test org\"]\n}\n```\n\n
\n\n---\n\n### `updateOrganization`\n\nUpdates an organization's profile information.\n\n`PATCH /v2/organization/{org_id}`\n\n```ts\nconst { data } = await client.updateOrganization(\n {\n org_id: 'example',\n },\n {\n id: '739224',\n name: 'Epilot',\n email: 'someone@epilot.cloud',\n phone: '+49123123123',\n website: 'https://epilot.cloud',\n address: {\n country: 'string',\n city: 'string',\n postal_code: 'string',\n street: 'string',\n street_number: 'string'\n },\n organization_use: 'Production',\n parent_production_org_id: 'string',\n created_date: '1970-01-01T00:00:00.000Z',\n logo_url: 'https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png',\n logo_thumbnail_url: 'https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png',\n signature: '

Thanks

',\n is_unlicensed_org: false,\n style: {},\n type: 'Vendor',\n symbol: 'EPI',\n pricing_tier: 'professional',\n free_user_limit: 50,\n tags: ['test org']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"739224\",\n \"name\": \"Epilot\",\n \"email\": \"someone@epilot.cloud\",\n \"phone\": \"+49123123123\",\n \"website\": \"https://epilot.cloud\",\n \"address\": {\n \"country\": \"string\",\n \"city\": \"string\",\n \"postal_code\": \"string\",\n \"street\": \"string\",\n \"street_number\": \"string\"\n },\n \"organization_use\": \"Production\",\n \"parent_production_org_id\": \"string\",\n \"created_date\": \"1970-01-01T00:00:00.000Z\",\n \"logo_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"logo_thumbnail_url\": \"https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png\",\n \"signature\": \"

Thanks

\",\n \"is_unlicensed_org\": false,\n \"style\": {},\n \"type\": \"Vendor\",\n \"symbol\": \"EPI\",\n \"pricing_tier\": \"professional\",\n \"free_user_limit\": 50,\n \"tags\": [\"test org\"]\n}\n```\n\n
\n\n---\n\n### `getSettings`\n\nRetrieves all configuration settings for an organization.\n\n`GET /v2/organization/{org_id}/settings`\n\n```ts\nconst { data } = await client.getSettings({\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"double_opt_in\": {\n \"enabled\": true\n },\n \"email_tracking\": {\n \"enabled\": true,\n \"track_opens\": true\n },\n \"default_language\": \"de\"\n}\n```\n\n
\n\n---\n\n### `putSettingsValue`\n\nCreates or updates a specific organization setting identified by its key.\n\n`PUT /v2/organization/{org_id}/settings/{key}`\n\n```ts\nconst { data } = await client.putSettingsValue(\n {\n org_id: 'example',\n key: 'example',\n },\n {\n enabled: true\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"enabled\": true\n}\n```\n\n
\n\n---\n\n### `deleteSettingsValue`\n\nRemoves a specific organization setting identified by its key.\n\n`DELETE /v2/organization/{org_id}/settings/{key}`\n\n```ts\nconst { data } = await client.deleteSettingsValue({\n org_id: 'example',\n key: 'example',\n})\n```\n\n---\n\n### `notifySettingsValueUpdated`\n\nSends an internal notification that an organization setting was updated.\n\n`POST /v2/organization/{org_id}/settings/{key}/notifications`\n\n```ts\nconst { data } = await client.notifySettingsValueUpdated(\n {\n org_id: 'example',\n key: 'example',\n },\n {\n user_email: 'string',\n org_name: 'string',\n value: {\n enabled: true\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"delivered\": true\n}\n```\n\n
\n\n---\n\n### `getOrganizationFeatureSettings`\n\nGet organization feature settings.\n\n`GET /v2/organization/feature-settings`\n\n```ts\nconst { data } = await client.getOrganizationFeatureSettings()\n```\n\n---\n\n### `getActiveAnnouncement`\n\nReturns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the\ncaller's organization.\n\n`GET /v2/organization/announcements/active`\n\n```ts\nconst { data } = await client.getActiveAnnouncement()\n```\n\n
\nResponse\n\n```json\n{\n \"announcement\": {\n \"id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"type\": \"subprocessor_change\",\n \"message\": {\n \"de\": \"Wir haben unsere Liste der Subdienstleister aktualisiert (gültig ab 01.11.2026). Bitte prüfe die Änderungen.\",\n \"en\": \"We have updated our list of subprocessors (effective 01.11.2026). Please review the changes.\"\n },\n \"link_url\": \"string\",\n \"list_version\": \"string\",\n \"effective_date\": \"1970-01-01\",\n \"window_days\": 0,\n \"status\": \"draft\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"published_at\": \"1970-01-01T00:00:00.000Z\",\n \"published_by\": \"string\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"archived_at\": \"1970-01-01T00:00:00.000Z\",\n \"archived_by\": \"string\",\n \"materialized_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"can_acknowledge\": true\n}\n```\n\n
\n\n---\n\n### `acknowledgeAnnouncement`\n\nAcknowledges an announcement on behalf of the caller's organization. Only org admins\n(`organization:edit`) may acknowledge.\n\n`POST /v2/organization/announcements/{announcement_id}/acknowledge`\n\n```ts\nconst { data } = await client.acknowledgeAnnouncement(\n {\n announcement_id: 'example',\n },\n {\n portal: '360'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"announcement_id\": \"string\",\n \"org_id\": \"string\",\n \"org_name\": \"string\",\n \"status\": \"acknowledged\",\n \"acknowledged_by\": {\n \"user_id\": \"string\",\n \"name\": \"string\",\n \"email\": \"string\"\n },\n \"acknowledged_at\": \"1970-01-01T00:00:00.000Z\",\n \"portal\": \"360\",\n \"list_version\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n## Schemas\n\n### `OrganizationId`\n\nUnique identifier for an organization (tenant) in the epilot platform\n\n```ts\ntype OrganizationId = string\n```\n\n### `Organization`\n\nRepresents an epilot organization (tenant).\n\nAn organization contains all the configuration, branding, and contact information\nfor a tenant account on the epilot platform.\n\n\n```ts\ntype Organization = {\n id?: string\n name?: string\n email?: string\n phone?: string\n website?: string\n address?: {\n country?: string\n city?: string\n postal_code?: string\n street?: string\n street_number?: string\n }\n organization_use?: \"Production\" | \"Sandbox\" | \"Preview\"\n parent_production_org_id?: string\n created_date?: string // date-time\n logo_url?: string\n logo_thumbnail_url?: string\n signature?: string\n is_unlicensed_org?: boolean\n style?: Record\n type?: \"Vendor\" | \"Partner\"\n symbol?: string\n pricing_tier?: string\n free_user_limit?: number\n tags?: string[]\n}\n```\n\n### `Settings`\n\nA key-value map of all organization settings.\nKeys are setting identifiers and values can be any valid JSON type.\n\n\n```ts\ntype Settings = Record\n```\n\n### `SettingsValue`\n\nThe value of an organization setting.\nCan be any valid JSON type: string, number, boolean, array, or object.\n\n\n```ts\ntype SettingsValue = string | number | boolean | Record[] | Record\n```\n\n### `FeatureSettings`\n\nFeature settings metadata served to frontend applications.\n\n```ts\ntype FeatureSettings = {\n version: string\n feature_flags: Array<{\n feature_name: string\n title?: {\n key: { ... }\n default_value?: { ... }\n }\n description?: {\n key: { ... }\n default_value?: { ... }\n }\n badge?: \"alpha\" | \"beta\" | \"new\" | \"advanced\" | \"experiment\" | \"deprecated\"\n confetti?: boolean\n one_way?: boolean\n reverse_checked?: boolean\n is_visible_for_partner?: boolean\n visibility_rules: Array<{\n type: { ... }\n setting_key?: { ... }\n flag_name?: { ... }\n pricing_tier_classes?: { ... }\n action?: { ... }\n resource?: { ... }\n rule?: { ... }\n rules?: { ... }\n }>\n }>\n}\n```\n\n### `FeatureFlagMetadata`\n\nMetadata for a single feature flag toggle\n\n```ts\ntype FeatureFlagMetadata = {\n feature_name: string\n title?: {\n key: string\n default_value?: string\n }\n description?: {\n key: string\n default_value?: string\n }\n badge?: \"alpha\" | \"beta\" | \"new\" | \"advanced\" | \"experiment\" | \"deprecated\"\n confetti?: boolean\n one_way?: boolean\n reverse_checked?: boolean\n is_visible_for_partner?: boolean\n visibility_rules: Array<{\n type: \"always\" | \"never\" | \"advanced_mode\" | \"pricing_tier\" | \"pricing_tier_class\" | \"production_org\" | \"feature_flag\" | \"permission\" | \"setting_enabled\" | \"flag_enabled\" | \"not\" | \"and\" | \"or\"\n setting_key?: string\n flag_name?: string\n pricing_tier_classes?: string[]\n action?: string\n resource?: string\n rule?: { ... }\n rules?: { ... }[]\n }>\n}\n```\n\n### `I18nString`\n\nInternationalized string with a translation key and optional default value\n\n```ts\ntype I18nString = {\n key: string\n default_value?: string\n}\n```\n\n### `VisibilityRule`\n\nA rule that determines feature visibility. Rules are combined with AND logic\nat the top level. Supports boolean combinators (and, or, not) for complex logic.\n\n\n```ts\ntype VisibilityRule = {\n type: \"always\" | \"never\" | \"advanced_mode\" | \"pricing_tier\" | \"pricing_tier_class\" | \"production_org\" | \"feature_flag\" | \"permission\" | \"setting_enabled\" | \"flag_enabled\" | \"not\" | \"and\" | \"or\"\n setting_key?: string\n flag_name?: string\n pricing_tier_classes?: string[]\n action?: string\n resource?: string\n rule?: { ... }\n rules?: { ... }[]\n}\n```\n\n### `AnnouncementType`\n\n```ts\ntype AnnouncementType = \"subprocessor_change\"\n```\n\n### `AnnouncementMessage`\n\n```ts\ntype AnnouncementMessage = {\n de: string\n en?: string\n}\n```\n\n### `Announcement`\n\n```ts\ntype Announcement = {\n id: string // uuid\n type: \"subprocessor_change\"\n message: {\n de: string\n en?: string\n }\n link_url: string\n list_version: string\n effective_date: string // date\n window_days: number\n status: \"draft\" | \"published\" | \"archived\"\n created_at: string // date-time\n created_by?: string\n updated_at: string // date-time\n published_at?: string // date-time\n published_by?: string\n expires_at?: string // date-time\n archived_at?: string // date-time\n archived_by?: string\n materialized_at?: string // date-time\n}\n```\n\n### `AnnouncementOrganizationStatusValue`\n\n```ts\ntype AnnouncementOrganizationStatusValue = \"acknowledged\" | \"pending\" | \"expired\" | \"superseded\"\n```\n\n### `AnnouncementOrganizationStatus`\n\n```ts\ntype AnnouncementOrganizationStatus = {\n announcement_id: string\n org_id: string\n org_name?: string\n status: \"acknowledged\" | \"pending\" | \"expired\" | \"superseded\"\n acknowledged_by?: {\n user_id: string\n name?: string\n email?: string\n }\n acknowledged_at?: string // date-time\n portal?: string\n list_version: string\n recorded_at?: string // date-time\n}\n```\n\n### `ActiveAnnouncementResponse`\n\n```ts\ntype ActiveAnnouncementResponse = {\n announcement: {\n id: string // uuid\n type: \"subprocessor_change\"\n message: {\n de: { ... }\n en?: { ... }\n }\n link_url: string\n list_version: string\n effective_date: string // date\n window_days: number\n status: \"draft\" | \"published\" | \"archived\"\n created_at: string // date-time\n created_by?: string\n updated_at: string // date-time\n published_at?: string // date-time\n published_by?: string\n expires_at?: string // date-time\n archived_at?: string // date-time\n archived_by?: string\n materialized_at?: string // date-time\n }\n can_acknowledge: boolean\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 a5fb63f62..c2f722b61 100644 --- a/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts +++ b/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts @@ -307,6 +307,33 @@ export declare namespace Components { */ target_id: string; } + export interface CreatePreviewRequest { + /** + * Display name of the preview. Defaults to `Preview `. + */ + name?: string; + /** + * Internal callers only (e.g. the admin portal, with the source org in `x-ivy-org-id`): + * who gets invited to the preview org as owner; `user_id` or `email` is required. Users + * always invite themselves; sending this with a user token is rejected with 403. + * + */ + requester?: { + /** + * A user of the source org (preferred). They are invited, and the copy job acts as + * them (e.g. they own the copied dashboards). Email, name and language come from + * their user record. + * + */ + user_id?: string; + /** + * Used when `user_id` is not given. + */ + email?: string; // email + display_name?: string; + language?: "de" | "en"; + }; + } /** * Bulk-delete job header surfaced by `createDeleteJob`/`getDeleteJob`. */ @@ -419,6 +446,10 @@ export declare namespace Components { export type DeleteJobStatus = "pending" | "in_progress" | "succeeded" | "partial" | "failed" | "cancelled"; export interface ErrorResponse { status: number; + /** + * Machine-readable reason, where the operation documents one + */ + code?: string; error: string; } export interface HealthFinding { @@ -426,7 +457,7 @@ export declare namespace Components { * Stable finding identity — unchanged across rescans. */ id: string; - 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"; + 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"; severity: "error" | "warning" | "info"; /** * Config type carrying the problem. @@ -535,6 +566,11 @@ export declare namespace Components { status: "scanning" | "failed"; error?: string; } + export interface HealthSeverityCounts { + error: number; + warning: number; + info: number; + } /** * Result of an index rebuild operation */ @@ -588,6 +624,54 @@ export declare namespace Components { title?: string; } | null; } + export interface Preview { + id: string; + name: string; + status: /** + * - creating_org: the preview org is being created + * - copying_configuration: the sync job copying this org's configuration is running + * - inviting: the requester is being invited to the preview org + * - ready: the requester got the "your preview org is ready" email + * - failed: see `error` + * + */ + PreviewStatus; + /** + * The org the preview copies (the org it was requested from). + */ + source_org_id: string; + /** + * The preview org, once created. + */ + preview_org_id?: string; + /** + * The sync job copying the configuration (see `getSyncJob`). + */ + sync_job_id?: string; + /** + * Final status of the copy sync job (`succeeded` or `partial`). + */ + sync_status?: string; + requested_by: { + user_id?: string; + email?: string; + }; + created_at: string; // date-time + updated_at: string; // date-time + /** + * Why the preview failed. + */ + error?: string; + } + /** + * - creating_org: the preview org is being created + * - copying_configuration: the sync job copying this org's configuration is running + * - inviting: the requester is being invited to the preview org + * - ready: the requester got the "your preview org is ready" email + * - failed: see `error` + * + */ + export type PreviewStatus = "creating_org" | "copying_configuration" | "inviting" | "ready" | "failed"; /** * Configuration resource type identifier. * Matches blueprint-manifest-api V3 naming conventions. @@ -724,6 +808,13 @@ export declare namespace Components { current_batch?: /* Position within the current topological batch for the active phase. */ SyncJobBatch; started_at: string; // date-time finished_at?: string; // date-time + health_check?: /** + * Compact state of the job's post-import health check. Full results: + * `getSyncJobHealth`. Absent on plans and on jobs that finished before + * the check existed. + * + */ + SyncJobHealthCheck; /** * Deprecated — use `listSyncJobEvents`. Up to 10 op-log events, * ordered by resource index rather than recency. @@ -1055,6 +1146,67 @@ export declare namespace Components { */ source: "events" | "op_log"; } + /** + * Targeted health check of the configs a sync job wrote into the target + * org. Findings carry `first_seen` = `last_seen` = `finished_at`. + * + */ + export interface SyncJobHealth { + status: /** + * State of a sync job's post-import health check. `pending`: queued, + * `running`: scanning, `done`: findings available, `failed`: the check + * itself failed or did not finish (the import is unaffected), `skipped`: + * nothing to check (a plan, a job that wrote nothing, or a cancelled + * job), `not_run`: a finished job that never had a check (start one with + * `runSyncJobHealth`). + * + */ + SyncJobHealthStatus; + started_at?: string; // date-time + finished_at?: string; // date-time + /** + * Written configs whose payload was read. + */ + scanned: number; + counts: HealthSeverityCounts; + /** + * Why the check failed or was skipped. + */ + error?: string; + /** + * Errors first, then warnings, then info. Empty unless `done`. + */ + findings: HealthFinding[]; + } + /** + * Compact state of the job's post-import health check. Full results: + * `getSyncJobHealth`. Absent on plans and on jobs that finished before + * the check existed. + * + */ + export interface SyncJobHealthCheck { + status: /** + * State of a sync job's post-import health check. `pending`: queued, + * `running`: scanning, `done`: findings available, `failed`: the check + * itself failed or did not finish (the import is unaffected), `skipped`: + * nothing to check (a plan, a job that wrote nothing, or a cancelled + * job), `not_run`: a finished job that never had a check (start one with + * `runSyncJobHealth`). + * + */ + SyncJobHealthStatus; + counts: HealthSeverityCounts; + } + /** + * State of a sync job's post-import health check. `pending`: queued, + * `running`: scanning, `done`: findings available, `failed`: the check + * itself failed or did not finish (the import is unaffected), `skipped`: + * nothing to check (a plan, a job that wrote nothing, or a cancelled + * job), `not_run`: a finished job that never had a check (start one with + * `runSyncJobHealth`). + * + */ + export type SyncJobHealthStatus = "pending" | "running" | "done" | "failed" | "skipped" | "not_run"; /** * Cursor-paginated list of sync jobs. */ @@ -1755,6 +1907,14 @@ export declare namespace Paths { export type $400 = Components.Schemas.ErrorResponse; } } + namespace CreatePreview { + export type RequestBody = Components.Schemas.CreatePreviewRequest; + namespace Responses { + export type $202 = Components.Schemas.Preview; + export interface $409 { + } + } + } namespace CreateSyncJob { export type RequestBody = /** * Request body for `createSyncJob`. `target_auth_token` is the destination @@ -1848,6 +2008,19 @@ export declare namespace Paths { export type $200 = /* Current index build state */ Components.Schemas.IndexStatusResponse; } } + namespace GetPreview { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $200 = Components.Schemas.Preview; + export interface $404 { + } + } + } namespace GetSyncJob { namespace Parameters { export type Id = string; @@ -1865,6 +2038,23 @@ export declare namespace Paths { export type $404 = Components.Schemas.ErrorResponse; } } + namespace GetSyncJobHealth { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $200 = /** + * Targeted health check of the configs a sync job wrote into the target + * org. Findings carry `first_seen` = `last_seen` = `finished_at`. + * + */ + Components.Schemas.SyncJobHealth; + export type $404 = Components.Schemas.ErrorResponse; + } + } namespace GetSyncPlan { namespace Parameters { export type Id = string; @@ -1975,6 +2165,13 @@ export declare namespace Paths { export type $200 = /* Cursor-paginated list of delete jobs. */ Components.Schemas.DeleteJobListResponse; } } + namespace ListPreviews { + namespace Responses { + export interface $200 { + results: Components.Schemas.Preview[]; + } + } + } namespace ListSyncJobEvents { namespace Parameters { export type After = string; @@ -2183,6 +2380,24 @@ export declare namespace Paths { export type $404 = Components.Schemas.ErrorResponse; } } + namespace RunSyncJobHealth { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $202 = /** + * Targeted health check of the configs a sync job wrote into the target + * org. Findings carry `first_seen` = `last_seen` = `finished_at`. + * + */ + Components.Schemas.SyncJobHealth; + export type $404 = Components.Schemas.ErrorResponse; + export type $409 = Components.Schemas.ErrorResponse; + } + } namespace SetHealthFindingsIgnored { export interface RequestBody { finding_ids: string[]; @@ -2494,6 +2709,50 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * getSyncJobHealth - getSyncJobHealth + * + * Targeted health check of an applied (non-dry-run) sync job. When the job + * reaches its terminal status, the target org is scanned asynchronously + * for the configs the job wrote — rows with status `created` or + * `patched` and a `target_id` — using the same finding logic as + * `getHealthReport`, plus `source_org_reference` for written payloads + * that still carry a source-org id, and `secret_value_missing` (warning) + * for a referenced SecretString variable the job created: a sync copies + * a variable's key, never its value, so setting it is a manual step. It runs for `succeeded` and + * `partial` jobs, and for `failed` jobs that wrote at least one config; + * otherwise it is `skipped`. Best-effort: it never changes the job's own + * status, and its results never replace the org-wide health report. + * Readable from both the source and the target org. + * + * A finished job that never had a check (it finished before checks + * existed) reports `not_run`. A check left `pending` or `running` for + * longer than the scan can take is reported as `failed`; both can be + * started again with `runSyncJobHealth`. + * + */ + 'getSyncJobHealth'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * runSyncJobHealth - runSyncJobHealth + * + * Start the targeted health check of a finished, applied sync job again: + * for jobs that finished before checks existed (`not_run`), checks that + * failed or got stuck, or to re-check after fixing something. Same scope + * and rules as the automatic check after every import. Callable from the + * source or the target org; the scan runs in the target org. Answers with + * the check's new state (`pending`) and runs asynchronously; poll + * `getSyncJobHealth`. + * + */ + 'runSyncJobHealth'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse /** * retrySyncJob - retrySyncJob * @@ -2766,6 +3025,42 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * listPreviews - listPreviews + * + * Lists the previews requested from the caller's current org, newest first (max 50). + */ + 'listPreviews'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createPreview - createPreview + * + * Starts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`, + * plus `requester`): a new, temporary org with + * `organization_use: Preview` (parent = this org) that receives a copy of all of + * this org's configuration through a sync job. When the copy is done, the + * requesting user is invited to the preview org as owner with a + * "your preview org is ready" email. Runs asynchronously; poll `getPreview`. + * + */ + 'createPreview'( + parameters?: Parameters | null, + data?: Paths.CreatePreview.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getPreview - getPreview + * + * Returns a preview requested from the caller's current org, with its progress. + */ + 'getPreview'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse } export interface PathsDictionary { @@ -3063,6 +3358,54 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v1/configs/sync-jobs/{id}/health']: { + /** + * getSyncJobHealth - getSyncJobHealth + * + * Targeted health check of an applied (non-dry-run) sync job. When the job + * reaches its terminal status, the target org is scanned asynchronously + * for the configs the job wrote — rows with status `created` or + * `patched` and a `target_id` — using the same finding logic as + * `getHealthReport`, plus `source_org_reference` for written payloads + * that still carry a source-org id, and `secret_value_missing` (warning) + * for a referenced SecretString variable the job created: a sync copies + * a variable's key, never its value, so setting it is a manual step. It runs for `succeeded` and + * `partial` jobs, and for `failed` jobs that wrote at least one config; + * otherwise it is `skipped`. Best-effort: it never changes the job's own + * status, and its results never replace the org-wide health report. + * Readable from both the source and the target org. + * + * A finished job that never had a check (it finished before checks + * existed) reports `not_run`. A check left `pending` or `running` for + * longer than the scan can take is reported as `failed`; both can be + * started again with `runSyncJobHealth`. + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/configs/sync-jobs/{id}/health:run']: { + /** + * runSyncJobHealth - runSyncJobHealth + * + * Start the targeted health check of a finished, applied sync job again: + * for jobs that finished before checks existed (`not_run`), checks that + * failed or got stuck, or to re-check after fixing something. Same scope + * and rules as the automatic check after every import. Callable from the + * source or the target org; the scan runs in the target org. Answers with + * the check's new state (`pending`) and runs asynchronously; poll + * `getSyncJobHealth`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } ['/v1/configs/sync-jobs/{id}/retry']: { /** * retrySyncJob - retrySyncJob @@ -3365,6 +3708,46 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v1/previews']: { + /** + * createPreview - createPreview + * + * Starts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`, + * plus `requester`): a new, temporary org with + * `organization_use: Preview` (parent = this org) that receives a copy of all of + * this org's configuration through a sync job. When the copy is done, the + * requesting user is invited to the preview org as owner with a + * "your preview org is ready" email. Runs asynchronously; poll `getPreview`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.CreatePreview.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * listPreviews - listPreviews + * + * Lists the previews requested from the caller's current org, newest first (max 50). + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/previews/{id}']: { + /** + * getPreview - getPreview + * + * Returns a preview requested from the caller's current org, with its progress. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } } export type Client = OpenAPIClient @@ -3382,6 +3765,7 @@ export type ConfigListResponse = Components.Schemas.ConfigListResponse; export type ConfigNode = Components.Schemas.ConfigNode; export type ConfigTypeInfo = Components.Schemas.ConfigTypeInfo; export type ConfirmLineageRequest = Components.Schemas.ConfirmLineageRequest; +export type CreatePreviewRequest = Components.Schemas.CreatePreviewRequest; export type DeleteJob = Components.Schemas.DeleteJob; export type DeleteJobCounts = Components.Schemas.DeleteJobCounts; export type DeleteJobListResponse = Components.Schemas.DeleteJobListResponse; @@ -3394,10 +3778,13 @@ export type ErrorResponse = Components.Schemas.ErrorResponse; export type HealthFinding = Components.Schemas.HealthFinding; export type HealthReportResponse = Components.Schemas.HealthReportResponse; export type HealthScanStartResponse = Components.Schemas.HealthScanStartResponse; +export type HealthSeverityCounts = Components.Schemas.HealthSeverityCounts; export type IndexRebuildResponse = Components.Schemas.IndexRebuildResponse; export type IndexStatusResponse = Components.Schemas.IndexStatusResponse; export type LineageEntryResponse = Components.Schemas.LineageEntryResponse; export type MatchSuggestion = Components.Schemas.MatchSuggestion; +export type Preview = Components.Schemas.Preview; +export type PreviewStatus = Components.Schemas.PreviewStatus; export type ResourceType = Components.Schemas.ResourceType; export type SnapshotInventoryItem = Components.Schemas.SnapshotInventoryItem; export type SnapshotInventorySkippedType = Components.Schemas.SnapshotInventorySkippedType; @@ -3411,6 +3798,9 @@ export type SyncJobCounts = Components.Schemas.SyncJobCounts; export type SyncJobEvent = Components.Schemas.SyncJobEvent; export type SyncJobEventDependency = Components.Schemas.SyncJobEventDependency; export type SyncJobEventListResponse = Components.Schemas.SyncJobEventListResponse; +export type SyncJobHealth = Components.Schemas.SyncJobHealth; +export type SyncJobHealthCheck = Components.Schemas.SyncJobHealthCheck; +export type SyncJobHealthStatus = Components.Schemas.SyncJobHealthStatus; export type SyncJobListResponse = Components.Schemas.SyncJobListResponse; export type SyncJobRequest = Components.Schemas.SyncJobRequest; export type SyncJobResource = Components.Schemas.SyncJobResource; diff --git a/packages/epilot-sdk-v2/src/types/organization.d.ts b/packages/epilot-sdk-v2/src/types/organization.d.ts index ceddbab86..7008274b8 100644 --- a/packages/epilot-sdk-v2/src/types/organization.d.ts +++ b/packages/epilot-sdk-v2/src/types/organization.d.ts @@ -8,103 +8,117 @@ import type { } from 'openapi-client-axios'; export declare namespace Components { + namespace Parameters { + export type AnnouncementIdPathParam = string; + } + export interface PathParameters { + AnnouncementIdPathParam?: Parameters.AnnouncementIdPathParam; + } namespace Schemas { - /** - * Request payload for creating a new organization - */ - export interface CreateOrganizationRequest { - /** - * Core organization information for the new tenant - */ - organization_detail?: { + export interface ActiveAnnouncementResponse { + announcement: { + id: string; // uuid + type: AnnouncementType; + message: AnnouncementMessage; + link_url: string; + list_version: string; + effective_date: string; // date + window_days: number; + status: "draft" | "published" | "archived"; + created_at: string; // date-time /** - * Display name for the organization - * example: - * epilot + * Internal caller that created the announcement (not returned to customers) */ - name: string; + created_by?: string; + updated_at: string; // date-time + published_at?: string; // date-time /** - * Primary contact email for the organization - * example: - * epilot@epilot.cloud + * Internal caller that published the announcement (not returned to customers) */ - email_address?: string; + published_by?: string; /** - * Organization type classification. - * - Vendor: Primary organization type for epilot customers - * - Partner: Partner organization type - * - * example: - * Vendor + * published_at + window_days */ - type: string; + expires_at?: string; // date-time + archived_at?: string; // date-time + archived_by?: string; /** - * Whether this is a production or sandbox environment. - * Sandbox organizations are for testing and linked to a production org. - * + * When the daily job wrote expired/superseded records after the window closed */ - organization_use?: "Production" | "Sandbox"; - /** - * The ULID of the pricing tier to assign to this organization. - * Determines feature access and billing. - * - * example: - * 01GEKHZHSN19KK10ZS92Y3WY9B - */ - pricing_tier_id: string; - }; + materialized_at?: string; // date-time + } | null; /** - * Initial owner user details. - * This user will receive an invitation email to complete registration - * and become the organization administrator. - * + * Whether the caller may acknowledge (organization:edit) */ - owner_user?: { - /** - * Full name of the owner user - * example: - * Ny Huynh - */ - full_name?: string; - /** - * Email address for the invitation (must be unique in the platform) - * example: - * ny.huynhthi@axonactive.com - */ - email_address: string; - }; + can_acknowledge: boolean; } - /** - * Data point metrics for a single organization, used for usage tracking and billing - */ - export interface DataPoint { + export interface Announcement { + id: string; // uuid + type: AnnouncementType; + message: AnnouncementMessage; + link_url: string; + list_version: string; + effective_date: string; // date + window_days: number; + status: "draft" | "published" | "archived"; + created_at: string; // date-time + /** + * Internal caller that created the announcement (not returned to customers) + */ + created_by?: string; + updated_at: string; // date-time + published_at?: string; // date-time + /** + * Internal caller that published the announcement (not returned to customers) + */ + published_by?: string; + /** + * published_at + window_days + */ + expires_at?: string; // date-time + archived_at?: string; // date-time + archived_by?: string; + /** + * When the daily job wrote expired/superseded records after the window closed + */ + materialized_at?: string; // date-time + } + export interface AnnouncementMessage { /** - * The organization ID (numeric format) * example: - * 206801 + * Wir haben unsere Liste der Subdienstleister aktualisiert (gültig ab 01.11.2026). Bitte prüfe die Änderungen. */ - id?: number; + de: string; /** - * Maximum number of customer entities recorded in the previous month. - * Used as a high-water mark for billing calculations. - * * example: - * 10 + * We have updated our list of subprocessors (effective 01.11.2026). Please review the changes. */ - max_customer?: number; + en?: string; + } + export interface AnnouncementOrganizationStatus { + announcement_id: string; + org_id: string; + org_name?: string; + status: AnnouncementOrganizationStatusValue; + acknowledged_by?: { + user_id: string; + name?: string; + email?: string; + }; + acknowledged_at?: string; // date-time /** - * Current count of customer entities in the organization. - * Represents the actual usage at the time of the query. - * * example: - * 10 + * 360 */ - actual_customer?: number; + portal?: string; + list_version: string; + /** + * When the record was written (acknowledgement or materialization by the daily job) + */ + recorded_at?: string; // date-time } - /** - * List of data point metrics for all organizations - */ - export type DataPointsResponse = /* Data point metrics for a single organization, used for usage tracking and billing */ DataPoint[]; + export type AnnouncementOrganizationStatusValue = "acknowledged" | "pending" | "expired" | "superseded"; + export type AnnouncementType = "subprocessor_change"; /** * Metadata for a single feature flag toggle */ @@ -162,172 +176,6 @@ export declare namespace Components { */ feature_flags: /* Metadata for a single feature flag toggle */ FeatureFlagMetadata[]; } - /** - * Response containing a list of HubSpot companies matching the search criteria - */ - export interface HubspotCompaniesResponse { - /** - * List of HubSpot companies matching the query - */ - results?: /** - * Represents a company record from HubSpot CRM synchronized to the epilot data warehouse. - * Used for CRM integration and organization mapping. - * - */ - HubspotCompany[]; - /** - * Total number of companies in the response - * example: - * 10 - */ - total?: number; - } - /** - * Represents a company record from HubSpot CRM synchronized to the epilot data warehouse. - * Used for CRM integration and organization mapping. - * - */ - export interface HubspotCompany { - /** - * The unique HubSpot company identifier - * example: - * 12345678901 - */ - company_id?: string; - /** - * The display name of the company in HubSpot - * example: - * Acme Corp - */ - company_name?: string | null; - /** - * The primary web domain associated with the company - * example: - * acme.com - */ - domain?: string | null; - } - /** - * HubSpot company data associated with an epilot organization. - * Contains business metrics and CRM properties synced from HubSpot. - * - */ - export interface HubspotOrganizationData { - /** - * The unique HubSpot company identifier - * example: - * 5278308807 - */ - company_id?: string; - /** - * The display name of the company in HubSpot - * example: - * Stadtwerke Meerbusch Willich Netz - */ - company_name?: string | null; - /** - * The primary web domain associated with the company - * example: - * stadtwerke-service.de - */ - domain?: string | null; - /** - * Current Monthly Recurring Revenue - * example: - * 11033.33 - */ - current_mrr?: string | null; - /** - * Potential Monthly Recurring Revenue - * example: - * 20000 - */ - potential_mrr?: string | null; - /** - * Company size classification - * example: - * Large - */ - company_size?: string | null; - /** - * Current pricing tier - * example: - * Professional - */ - pricing_tier?: string | null; - /** - * HubSpot lifecycle stage - * example: - * customer - */ - lifecyclestage?: string | null; - /** - * Industry classification - * example: - * Energiedienstleister - */ - industry?: string | null; - /** - * Number of employees - * example: - * 200 - */ - numberofemployees?: string | null; - /** - * epilot customer number - * example: - * 10031 - */ - customer_number?: string | null; - /** - * Number of active users - * example: - * 34 - */ - no_of_users?: string | null; - /** - * User activation rate - * example: - * 0.647059 - */ - activation_rate?: string | null; - /** - * List of active features - * example: - * Installer Portal - */ - active_features?: string | null; - /** - * Implemented use cases - * example: - * GRID: Installations (MGA);GRID: House Connections - */ - usecases_implemented?: string | null; - /** - * Current buyer's journey stage - * example: - * Expansion-Ready - */ - buyer_journey?: string | null; - /** - * Company country - * example: - * Germany - */ - country?: string | null; - /** - * Company city - * example: - * Willich - */ - city?: string | null; - /** - * Full HubSpot properties object with all available fields - */ - properties?: { - [name: string]: any; - }; - } /** * Internationalized string with a translation key and optional default value */ @@ -345,124 +193,6 @@ export declare namespace Components { */ default_value?: string; } - /** - * Extended organization object with internal-only fields. - * - * Includes all fields from the base Organization schema plus additional - * internal metadata used for platform administration and support. - * - */ - export interface InternalOrganization { - id?: /** - * Unique identifier for an organization (tenant) in the epilot platform - * example: - * 739224 - */ - OrganizationId; - /** - * Organization name - * example: - * Epilot - */ - name?: string | null; - /** - * Organization contact email - * example: - * someone@epilot.cloud - */ - email?: string | null; - /** - * Organization contact phone - * example: - * +49123123123 - */ - phone?: string | null; - /** - * Organization website - * example: - * https://epilot.cloud - */ - website?: string | null; - /** - * Organization postal address - */ - address?: { - [name: string]: any; - country?: string | null; - city?: string | null; - postal_code?: string | null; - street?: string | null; - street_number?: string | null; - }; - /** - * Indicates whether this is a production or sandbox organization. - * Sandbox organizations are linked to a parent production organization for testing purposes. - * - */ - organization_use?: "Production" | "Sandbox"; - /** - * The ID of the parent production organization. - * Only set for sandbox organizations to link them to their production counterpart. - * - */ - parent_production_org_id?: string | null; - /** - * Organization created date - */ - created_date?: string; // date-time - /** - * Organization logo URL - * example: - * https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png - */ - logo_url?: string | null; - /** - * Organization logo thumbnail URL - * example: - * https://epilot-playground-organization-data.s3.eu-central-1.amazonaws.com/epilot-logo.png - */ - logo_thumbnail_url?: string | null; - /** - * Default email signature of organization - * example: - *

Thanks

- */ - signature?: string | null; - /** - * example: - * false - */ - is_unlicensed_org?: boolean | null; - /** - * Organization style setting (e.g. font setting) - */ - style?: { - [name: string]: any; - } | null; - type?: "Vendor" | "Partner"; - /** - * example: - * EPI - */ - symbol?: string | null; - /** - * Deprecated - Please use pricing tier api - * example: - * professional - */ - pricing_tier?: string | null; - /** - * example: - * 50 - */ - free_user_limit?: number | null; - /** - * Labels/tags associated with the organization for categorization and filtering. - * Used internally to classify organizations (e.g., "enterprise", "beta-tester", "test org"). - * - */ - tags?: string[] | null; - } /** * Represents an epilot organization (tenant). * @@ -513,11 +243,12 @@ export declare namespace Components { street_number?: string | null; }; /** - * Indicates whether this is a production or sandbox organization. + * Indicates whether this is a production, sandbox or preview organization. * Sandbox organizations are linked to a parent production organization for testing purposes. + * Preview organizations are temporary copies of a production organization. * */ - organization_use?: "Production" | "Sandbox"; + organization_use?: "Production" | "Sandbox" | "Preview"; /** * The ID of the parent production organization. * Only set for sandbox organizations to link them to their production counterpart. @@ -581,109 +312,12 @@ export declare namespace Components { */ tags?: string[] | null; } - /** - * Records the cleanup status reported by a specific service for an organization. - * Each service that stores organization data reports its cleanup operations here. - * - */ - export interface OrganizationCleanupStatus { - org_id: /** - * Unique identifier for an organization (tenant) in the epilot platform - * example: - * 739224 - */ - OrganizationId; - /** - * Name of the service reporting its cleanup status - * example: - * organization-api - */ - service_name: string; - /** - * List of cleanup operations performed by the service. - * Each operation describes what action was taken on which resource. - * - * example: - * [ - * { - * "action": "Delete", - * "resource": "OrgTable", - * "extra_info": "pk: ORG#739224" - * }, - * { - * "action": "Archive", - * "resource": "s3://my-bucket", - * "extra_info": "my-bucket/my-key.txt" - * } - * ] - */ - operations?: { - [name: string]: any; - /** - * The type of cleanup action performed. - * Common values: Delete, Archive, Purge - * - * example: - * Delete - */ - action?: string; - /** - * The resource that was cleaned up. - * Can be a table name, S3 bucket, or other resource identifier. - * - * example: - * OrgTable - */ - resource?: string; - /** - * Additional context about the cleanup operation - */ - extra_info?: /* Additional context about the cleanup operation */ string | number | boolean | { - [name: string]: any; - }[] | { - [name: string]: any; - }; - }[]; - } /** * Unique identifier for an organization (tenant) in the epilot platform * example: * 739224 */ export type OrganizationId = string; - /** - * Represents an organization that has been marked for deletion and requires cleanup. - * Contains metadata about the deletion request. - * - */ - export interface OrganizationToCleanup { - org_id: /** - * Unique identifier for an organization (tenant) in the epilot platform - * example: - * 739224 - */ - OrganizationId; - /** - * Timestamp when the organization was marked for deletion - * example: - * 2021-06-01T00:00:00Z - */ - deleted_at: string; // date-time - /** - * User ID of the person who initiated the deletion - * example: - * 123456 - */ - deleted_by: string; - } - /** - * A unique key identifying an organization setting. - * Common setting keys include: double_opt_in, email_tracking, default_language, workflow_notifications - * - * example: - * double_opt_in - */ - export type SettingKey = string; /** * A key-value map of all organization settings. * Keys are setting identifiers and values can be any valid JSON type. @@ -735,7 +369,7 @@ export declare namespace Components { /** * The rule type discriminator */ - type: "always" | "never" | "advanced_mode" | "pricing_tier" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or"; + type: "always" | "never" | "advanced_mode" | "pricing_tier" | "pricing_tier_class" | "production_org" | "feature_flag" | "permission" | "setting_enabled" | "flag_enabled" | "not" | "and" | "or"; /** * Setting key for pricing_tier and setting_enabled rules */ @@ -744,6 +378,15 @@ export declare namespace Components { * Flag name for feature_flag and flag_enabled rules */ flag_name?: string; + /** + * Pricing tier class names matched (word-boundary, case-insensitive) for pricing_tier_class rules + * example: + * [ + * "enterprise", + * "professional" + * ] + */ + pricing_tier_classes?: string[]; /** * Permission action for permission rules */ @@ -771,6 +414,33 @@ export declare namespace Components { } } export declare namespace Paths { + namespace AcknowledgeAnnouncement { + namespace Parameters { + export type AnnouncementId = string; + } + export interface PathParameters { + announcement_id: Parameters.AnnouncementId; + } + export interface RequestBody { + /** + * Portal the acknowledgement was given in + * example: + * 360 + */ + portal?: string; + } + namespace Responses { + export type $200 = Components.Schemas.AnnouncementOrganizationStatus; + export interface $401 { + } + export interface $403 { + } + export interface $404 { + } + export interface $409 { + } + } + } namespace DeleteSettingsValue { namespace Parameters { export type Key = string; @@ -796,6 +466,13 @@ export declare namespace Paths { } } } + namespace GetActiveAnnouncement { + namespace Responses { + export type $200 = Components.Schemas.ActiveAnnouncementResponse; + export interface $401 { + } + } + } namespace GetCurrentOrganization { namespace Responses { export type $200 = /** @@ -893,6 +570,50 @@ export declare namespace Paths { } } } + namespace NotifySettingsValueUpdated { + namespace Parameters { + export type Key = string; + export type OrgId = /** + * Unique identifier for an organization (tenant) in the epilot platform + * example: + * 739224 + */ + Components.Schemas.OrganizationId; + } + export interface PathParameters { + org_id: Parameters.OrgId; + key: Parameters.Key; + } + export interface RequestBody { + /** + * Email of the staff user who changed the setting + */ + user_email?: string; + /** + * Organization display name + */ + org_name?: string; + value?: /** + * The value of an organization setting. + * Can be any valid JSON type: string, number, boolean, array, or object. + * + * example: + * { + * "enabled": true + * } + */ + Components.Schemas.SettingsValue; + } + namespace Responses { + export interface $200 { + delivered?: boolean; + } + export interface $401 { + } + export interface $403 { + } + } + } namespace PutSettingsValue { namespace Parameters { export type Key = string; @@ -1084,7 +805,19 @@ export interface OperationMethods { config?: AxiosRequestConfig ): OperationResponse /** - * getOrganizationFeatureSettings - Get organization feature settings + * notifySettingsValueUpdated - notifySettingsValueUpdated + * + * Sends an internal notification that an organization setting was updated. + */ + 'notifySettingsValueUpdated'( + parameters?: Parameters | null, + data?: Paths.NotifySettingsValueUpdated.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getOrganizationFeatureSettings - getOrganizationFeatureSettings + * + * Get organization feature settings. * * Returns feature flag metadata for the organization UI. * @@ -1098,15 +831,51 @@ export interface OperationMethods { config?: AxiosRequestConfig ): OperationResponse /** - * getFeatureSettings - Get platform configuration metadata + * getFeatureSettings - getFeatureSettings + * + * Get platform configuration metadata. * * Deprecated: use /v2/organization/feature-settings instead. + * */ 'getFeatureSettings'( parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * getActiveAnnouncement - getActiveAnnouncement + * + * Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the + * caller's organization. + * + * Only the newest published announcement whose acknowledgement window is still open is returned, and + * only if the organization is eligible (created before the announcement was published) and has not + * acknowledged it yet. Otherwise `announcement` is `null`. + * + * `can_acknowledge` tells whether the caller may acknowledge (requires `organization:edit`). + * + */ + 'getActiveAnnouncement'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * acknowledgeAnnouncement - acknowledgeAnnouncement + * + * Acknowledges an announcement on behalf of the caller's organization. Only org admins + * (`organization:edit`) may acknowledge. + * + * One acknowledgement per organization: the first acknowledgement wins. Acknowledging an already + * acknowledged announcement returns the existing record. + * + */ + 'acknowledgeAnnouncement'( + parameters?: Parameters | null, + data?: Paths.AcknowledgeAnnouncement.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse } export interface PathsDictionary { @@ -1220,9 +989,23 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v2/organization/{org_id}/settings/{key}/notifications']: { + /** + * notifySettingsValueUpdated - notifySettingsValueUpdated + * + * Sends an internal notification that an organization setting was updated. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.NotifySettingsValueUpdated.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } ['/v2/organization/feature-settings']: { /** - * getOrganizationFeatureSettings - Get organization feature settings + * getOrganizationFeatureSettings - getOrganizationFeatureSettings + * + * Get organization feature settings. * * Returns feature flag metadata for the organization UI. * @@ -1238,9 +1021,12 @@ export interface PathsDictionary { } ['/v2/feature-settings']: { /** - * getFeatureSettings - Get platform configuration metadata + * getFeatureSettings - getFeatureSettings + * + * Get platform configuration metadata. * * Deprecated: use /v2/organization/feature-settings instead. + * */ 'get'( parameters?: Parameters | null, @@ -1248,26 +1034,59 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v2/organization/announcements/active']: { + /** + * getActiveAnnouncement - getActiveAnnouncement + * + * Returns the announcement (e.g. a subprocessor change notice) to show as a portal banner for the + * caller's organization. + * + * Only the newest published announcement whose acknowledgement window is still open is returned, and + * only if the organization is eligible (created before the announcement was published) and has not + * acknowledged it yet. Otherwise `announcement` is `null`. + * + * `can_acknowledge` tells whether the caller may acknowledge (requires `organization:edit`). + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v2/organization/announcements/{announcement_id}/acknowledge']: { + /** + * acknowledgeAnnouncement - acknowledgeAnnouncement + * + * Acknowledges an announcement on behalf of the caller's organization. Only org admins + * (`organization:edit`) may acknowledge. + * + * One acknowledgement per organization: the first acknowledgement wins. Acknowledging an already + * acknowledged announcement returns the existing record. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.AcknowledgeAnnouncement.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } } export type Client = OpenAPIClient -export type CreateOrganizationRequest = Components.Schemas.CreateOrganizationRequest; -export type DataPoint = Components.Schemas.DataPoint; -export type DataPointsResponse = Components.Schemas.DataPointsResponse; +export type ActiveAnnouncementResponse = Components.Schemas.ActiveAnnouncementResponse; +export type Announcement = Components.Schemas.Announcement; +export type AnnouncementMessage = Components.Schemas.AnnouncementMessage; +export type AnnouncementOrganizationStatus = Components.Schemas.AnnouncementOrganizationStatus; +export type AnnouncementOrganizationStatusValue = Components.Schemas.AnnouncementOrganizationStatusValue; +export type AnnouncementType = Components.Schemas.AnnouncementType; export type FeatureFlagMetadata = Components.Schemas.FeatureFlagMetadata; export type FeatureSettings = Components.Schemas.FeatureSettings; -export type HubspotCompaniesResponse = Components.Schemas.HubspotCompaniesResponse; -export type HubspotCompany = Components.Schemas.HubspotCompany; -export type HubspotOrganizationData = Components.Schemas.HubspotOrganizationData; export type I18nString = Components.Schemas.I18nString; -export type InternalOrganization = Components.Schemas.InternalOrganization; export type Organization = Components.Schemas.Organization; -export type OrganizationCleanupStatus = Components.Schemas.OrganizationCleanupStatus; export type OrganizationId = Components.Schemas.OrganizationId; -export type OrganizationToCleanup = Components.Schemas.OrganizationToCleanup; -export type SettingKey = Components.Schemas.SettingKey; export type Settings = Components.Schemas.Settings; export type SettingsValue = Components.Schemas.SettingsValue; export type VisibilityRule = Components.Schemas.VisibilityRule;