diff --git a/clients/address-client/src/openapi.d.ts b/clients/address-client/src/openapi.d.ts index 9c712dc8f..aefa7f567 100644 --- a/clients/address-client/src/openapi.d.ts +++ b/clients/address-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/address-suggestions-client/src/openapi.d.ts b/clients/address-suggestions-client/src/openapi.d.ts index 7ef51946d..af049fcd6 100644 --- a/clients/address-suggestions-client/src/openapi.d.ts +++ b/clients/address-suggestions-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/audit-logs-client/src/openapi.d.ts b/clients/audit-logs-client/src/openapi.d.ts index cd0080aa1..440abc6cd 100644 --- a/clients/audit-logs-client/src/openapi.d.ts +++ b/clients/audit-logs-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/configuration-hub-client/src/openapi.d.ts b/clients/configuration-hub-client/src/openapi.d.ts index 06bf5dfe0..defb78e64 100644 --- a/clients/configuration-hub-client/src/openapi.d.ts +++ b/clients/configuration-hub-client/src/openapi.d.ts @@ -1247,307 +1247,307 @@ declare namespace Paths { export interface OperationMethods { /** * listConfigTypes - listConfigTypes - * + * * Returns the static list of available configuration types with display metadata. * This is a cheap call — no fan-out to downstream APIs. Returns all known types * with labels and icons. The frontend should then call `listConfigs` separately * for each type it wants to load. - * + * */ 'listConfigTypes'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listConfigs - listConfigs - * + * * List configs of a given type with pagination. Returns summary metadata only * (not full payloads). The frontend calls this per type folder when expanding. - * + * * Supports offset-based pagination via `from` and `size` parameters. - * + * */ 'listConfigs'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getConfigDependencies - getConfigDependencies - * + * * Get configs that are referenced by the given config. * Used to render children when expanding a config node in the tree. - * + * * Resolves dependencies by fetching the config payload server-side and scanning * for references (UUIDs, source IDs, slug-based references). - * + * */ 'getConfigDependencies'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getConfigUsedBy - getConfigUsedBy - * + * * Get configs that reference the given config (reverse dependencies). * Scans the indexed config items for references to this config's ID or aliases. - * + * */ 'getConfigUsedBy'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getIndex - getIndex - * + * * Return the current index build state for the caller's organization. * Clients poll this to decide whether to show a "building" indicator * and when to refetch data. - * + * */ 'getIndex'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * compareConfigs - compareConfigs - * + * * Compare the caller org's configs of a single type against another * (source) org, side by side. Rows are paired via the lineage registry in * both sync directions (caller imported from source, or source imported * from caller); configs without a counterpart come back as `only_current` * or `only_source`. - * + * * `source_auth_token` must be a valid token for `source_org_id` — the * frontend mints one via the pipeline pairing, mirroring `createSyncJob`'s * `target_auth_token`. The token is verified against `source_org_id` * before any source-org data is read. - * + * * POST because the request carries a token; the operation reads only. - * + * */ 'compareConfigs'( parameters?: Parameters | null, data?: Paths.CompareConfigs.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * suggestMatches - suggestMatches - * + * * Run the sync-grade heuristic match (`lookupByHeuristic` — name / slug / * unique key) for a batch of source-org configs that have no lineage * entry, and return candidate counterparts in the caller's org. - * + * * Suggestions are ephemeral — nothing is persisted. The client offers * each candidate to the user, and a confirmed pair is written via * `confirmLineage`. Batches are capped at 25 ids; the client pages * through unmatched rows across successive calls. - * + * */ 'suggestMatches'( parameters?: Parameters | null, data?: Paths.SuggestMatches.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * confirmLineage - confirmLineage - * + * * Persist a lineage entry pairing a source-org config with a config in * the caller's org — used to confirm a heuristic suggestion from the * Compare view. Writes to the caller org's lineage partition (caller as * sync target), so subsequent syncs PATCH the confirmed target instead of * creating a duplicate. Conflicts (an existing entry pointing at a * different target) return 409 so the client can refresh. - * + * */ 'confirmLineage'( parameters?: Parameters | null, data?: Paths.ConfirmLineage.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * breakLineage - breakLineage - * + * * Delete a lineage entry from the caller org's partition (caller as sync * target), identified by `type` + `source_id`. Used to break a wrong or * stale match from the Compare view. Note: a future sync can re-match the * same pair heuristically — the entry is deleted, not blocklisted. To * break a match recorded in the OTHER direction, call this operation * authenticated as the other org (pipeline token). - * + * */ 'breakLineage'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listSyncJobs - listSyncJobs - * + * * List sync jobs scoped to the caller's organization, paginated with an opaque * cursor. Defaults to most-recent first. - * + * */ 'listSyncJobs'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * createSyncJob - createSyncJob - * + * * Create a new cross-org sync job. The job is enqueued for asynchronous execution * by the worker Lambda; the response returns the persisted job header with status * `pending`. - * + * * See `docs/sync/INTERFACES.md` for the locked request/response contract. - * + * */ 'createSyncJob'( parameters?: Parameters | null, data?: Paths.CreateSyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getSyncJob - getSyncJob - * + * * Fetch a single sync job by ID. Returns the job header, counts summary, * current phase pointer, and the latest activity events. Frontend polls this * endpoint with a ramping interval. - * + * */ 'getSyncJob'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * retrySyncJob - retrySyncJob - * + * * Retry the unresolved resources from a prior sync job: `failed` rows, plus * rows the original run left at `pending`/`in_progress` because it stopped * early. Creates a new job whose scope is that `(type, source_id)` set and * enqueues it for execution. Optionally accepts inline payload overrides. - * + * */ 'retrySyncJob'( parameters?: Parameters | null, data?: Paths.RetrySyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * cancelSyncJob - cancelSyncJob - * + * * Cancel a running sync job. Marks the job `cancelled` with a `finished_at` * so it stops being reported as in-flight, and the worker stops at its next * batch boundary — phases re-read the job status and abort rather than * overwrite a cancellation with their own outcome. - * + * * Resources already written to the target are NOT rolled back; cancelling * stops further work. Jobs already in a terminal state are rejected with * 409. - * + * */ 'cancelSyncJob'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listSyncJobResources - listSyncJobResources - * + * * List the per-resource rows for a sync job. Supports filtering by status * (e.g. `failed`) and cursor pagination. Used by the failures table and the * dry-run plan view in the frontend. - * + * */ 'listSyncJobResources'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listDeleteJobs - listDeleteJobs - * + * * List bulk-delete jobs scoped to the caller's organization, paginated * with an opaque cursor. Most-recent first. - * + * */ 'listDeleteJobs'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * createDeleteJob - createDeleteJob - * + * * Create a bulk-delete job for the caller's organization. The selected * resources are deleted asynchronously by a worker Lambda; the response * returns the persisted job header with status `pending`. - * + * * Raw delete — no dependency checks are performed. On completion the * worker auto-triggers an index rebuild so the config list reflects the * deletions. - * + * */ 'createDeleteJob'( parameters?: Parameters | null, data?: Paths.CreateDeleteJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getDeleteJob - getDeleteJob - * + * * Fetch a single bulk-delete job by ID. Returns the job header and counts * summary. Frontend polls this endpoint while the job runs. - * + * */ 'getDeleteJob'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listDeleteJobResources - listDeleteJobResources - * + * * List the per-resource rows for a delete job, cursor-paginated. Used by * the failures view in the frontend. - * + * */ 'listDeleteJobResources'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * rebuildIndex - rebuildIndex - * + * * Rebuild the configuration index for the caller's organization. * Fire-and-forget: invokes the async worker and returns immediately. * A new rebuild will cancel any in-flight build (see `build_token`). - * + * */ 'rebuildIndex'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getConfigInventory - getConfigInventory - * + * * Returns a fresh inventory of an org's configuration resources — `{ type, id }` identities only, * no full payloads. Calls every adapter's `list()` live (bypasses the 7-day DynamoDB index). * Intended for snapshot-api to consume when taking a full-org snapshot. - * + * */ 'getConfigInventory'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } @@ -1555,341 +1555,341 @@ export interface PathsDictionary { ['/v1/configs/types']: { /** * listConfigTypes - listConfigTypes - * + * * Returns the static list of available configuration types with display metadata. * This is a cheap call — no fan-out to downstream APIs. Returns all known types * with labels and icons. The frontend should then call `listConfigs` separately * for each type it wants to load. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/{type}']: { /** * listConfigs - listConfigs - * + * * List configs of a given type with pagination. Returns summary metadata only * (not full payloads). The frontend calls this per type folder when expanding. - * + * * Supports offset-based pagination via `from` and `size` parameters. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/{type}/{id}/dependencies']: { /** * getConfigDependencies - getConfigDependencies - * + * * Get configs that are referenced by the given config. * Used to render children when expanding a config node in the tree. - * + * * Resolves dependencies by fetching the config payload server-side and scanning * for references (UUIDs, source IDs, slug-based references). - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/{type}/{id}/used_by']: { /** * getConfigUsedBy - getConfigUsedBy - * + * * Get configs that reference the given config (reverse dependencies). * Scans the indexed config items for references to this config's ID or aliases. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/index']: { /** * getIndex - getIndex - * + * * Return the current index build state for the caller's organization. * Clients poll this to decide whether to show a "building" indicator * and when to refetch data. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/compare']: { /** * compareConfigs - compareConfigs - * + * * Compare the caller org's configs of a single type against another * (source) org, side by side. Rows are paired via the lineage registry in * both sync directions (caller imported from source, or source imported * from caller); configs without a counterpart come back as `only_current` * or `only_source`. - * + * * `source_auth_token` must be a valid token for `source_org_id` — the * frontend mints one via the pipeline pairing, mirroring `createSyncJob`'s * `target_auth_token`. The token is verified against `source_org_id` * before any source-org data is read. - * + * * POST because the request carries a token; the operation reads only. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.CompareConfigs.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/compare/suggestions']: { /** * suggestMatches - suggestMatches - * + * * Run the sync-grade heuristic match (`lookupByHeuristic` — name / slug / * unique key) for a batch of source-org configs that have no lineage * entry, and return candidate counterparts in the caller's org. - * + * * Suggestions are ephemeral — nothing is persisted. The client offers * each candidate to the user, and a confirmed pair is written via * `confirmLineage`. Batches are capped at 25 ids; the client pages * through unmatched rows across successive calls. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.SuggestMatches.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/lineage']: { /** * confirmLineage - confirmLineage - * + * * Persist a lineage entry pairing a source-org config with a config in * the caller's org — used to confirm a heuristic suggestion from the * Compare view. Writes to the caller org's lineage partition (caller as * sync target), so subsequent syncs PATCH the confirmed target instead of * creating a duplicate. Conflicts (an existing entry pointing at a * different target) return 409 so the client can refresh. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.ConfirmLineage.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * breakLineage - breakLineage - * + * * Delete a lineage entry from the caller org's partition (caller as sync * target), identified by `type` + `source_id`. Used to break a wrong or * stale match from the Compare view. Note: a future sync can re-match the * same pair heuristically — the entry is deleted, not blocklisted. To * break a match recorded in the OTHER direction, call this operation * authenticated as the other org (pipeline token). - * + * */ 'delete'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs']: { /** * createSyncJob - createSyncJob - * + * * Create a new cross-org sync job. The job is enqueued for asynchronous execution * by the worker Lambda; the response returns the persisted job header with status * `pending`. - * + * * See `docs/sync/INTERFACES.md` for the locked request/response contract. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.CreateSyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listSyncJobs - listSyncJobs - * + * * List sync jobs scoped to the caller's organization, paginated with an opaque * cursor. Defaults to most-recent first. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}']: { /** * getSyncJob - getSyncJob - * + * * Fetch a single sync job by ID. Returns the job header, counts summary, * current phase pointer, and the latest activity events. Frontend polls this * endpoint with a ramping interval. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}/retry']: { /** * retrySyncJob - retrySyncJob - * + * * Retry the unresolved resources from a prior sync job: `failed` rows, plus * rows the original run left at `pending`/`in_progress` because it stopped * early. Creates a new job whose scope is that `(type, source_id)` set and * enqueues it for execution. Optionally accepts inline payload overrides. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.RetrySyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}/cancel']: { /** * cancelSyncJob - cancelSyncJob - * + * * Cancel a running sync job. Marks the job `cancelled` with a `finished_at` * so it stops being reported as in-flight, and the worker stops at its next * batch boundary — phases re-read the job status and abort rather than * overwrite a cancellation with their own outcome. - * + * * Resources already written to the target are NOT rolled back; cancelling * stops further work. Jobs already in a terminal state are rejected with * 409. - * + * */ 'post'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}/resources']: { /** * listSyncJobResources - listSyncJobResources - * + * * List the per-resource rows for a sync job. Supports filtering by status * (e.g. `failed`) and cursor pagination. Used by the failures table and the * dry-run plan view in the frontend. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/delete-jobs']: { /** * createDeleteJob - createDeleteJob - * + * * Create a bulk-delete job for the caller's organization. The selected * resources are deleted asynchronously by a worker Lambda; the response * returns the persisted job header with status `pending`. - * + * * Raw delete — no dependency checks are performed. On completion the * worker auto-triggers an index rebuild so the config list reflects the * deletions. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.CreateDeleteJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listDeleteJobs - listDeleteJobs - * + * * List bulk-delete jobs scoped to the caller's organization, paginated * with an opaque cursor. Most-recent first. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/delete-jobs/{id}']: { /** * getDeleteJob - getDeleteJob - * + * * Fetch a single bulk-delete job by ID. Returns the job header and counts * summary. Frontend polls this endpoint while the job runs. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/delete-jobs/{id}/resources']: { /** * listDeleteJobResources - listDeleteJobResources - * + * * List the per-resource rows for a delete job, cursor-paginated. Used by * the failures view in the frontend. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/index:rebuild']: { /** * rebuildIndex - rebuildIndex - * + * * Rebuild the configuration index for the caller's organization. * Fire-and-forget: invokes the async worker and returns immediately. * A new rebuild will cancel any in-flight build (see `build_token`). - * + * */ 'post'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/inventory']: { /** * getConfigInventory - getConfigInventory - * + * * Returns a fresh inventory of an org's configuration resources — `{ type, id }` identities only, * no full payloads. Calls every adapter's `list()` live (bypasses the 7-day DynamoDB index). * Intended for snapshot-api to consume when taking a full-org snapshot. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } } diff --git a/clients/consent-client/src/openapi.d.ts b/clients/consent-client/src/openapi.d.ts index 3f9cc3757..e4bf03312 100644 --- a/clients/consent-client/src/openapi.d.ts +++ b/clients/consent-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/document-client/src/openapi.d.ts b/clients/document-client/src/openapi.d.ts index 50d0857cc..c90b6a959 100644 --- a/clients/document-client/src/openapi.d.ts +++ b/clients/document-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/email-template-client/src/openapi.d.ts b/clients/email-template-client/src/openapi.d.ts index e946c9298..7de2eb9fe 100644 --- a/clients/email-template-client/src/openapi.d.ts +++ b/clients/email-template-client/src/openapi.d.ts @@ -992,6 +992,7 @@ declare namespace Paths { } } + export interface OperationMethods { /** * saveTemplate - saveTemplate @@ -1158,6 +1159,7 @@ export interface PathsDictionary { export type Client = OpenAPIClient + export type ApproveAction = Components.Schemas.ApproveAction; export type ApprovedAt = Components.Schemas.ApprovedAt; export type AsyncEmailTemplateResponse = Components.Schemas.AsyncEmailTemplateResponse; diff --git a/clients/file-client/src/openapi.d.ts b/clients/file-client/src/openapi.d.ts index 604d89518..adc69eded 100644 --- a/clients/file-client/src/openapi.d.ts +++ b/clients/file-client/src/openapi.d.ts @@ -5374,23 +5374,23 @@ export interface OperationMethods { ): OperationResponse /** * getFileSummaryFeedback - Get file summary feedback - * + * * Get the authenticated user's feedback for the current generated file summary. */ 'getFileSummaryFeedback'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * putFileSummaryFeedback - Submit file summary feedback - * + * * Upsert thumbs up/down feedback for the current generated file summary. */ 'putFileSummaryFeedback'( parameters?: Parameters | null, data?: Paths.PutFileSummaryFeedback.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * createFileSummaryJob - createFileSummaryJob @@ -5917,23 +5917,23 @@ export interface PathsDictionary { ['/v1/files/{id}/summary/feedback']: { /** * getFileSummaryFeedback - Get file summary feedback - * + * * Get the authenticated user's feedback for the current generated file summary. */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * putFileSummaryFeedback - Submit file summary feedback - * + * * Upsert thumbs up/down feedback for the current generated file summary. */ 'put'( parameters?: Parameters | null, data?: Paths.PutFileSummaryFeedback.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/files/{id}/summary-jobs']: { diff --git a/clients/iban-client/src/openapi.d.ts b/clients/iban-client/src/openapi.d.ts index 8715d6206..ded6fa4f3 100644 --- a/clients/iban-client/src/openapi.d.ts +++ b/clients/iban-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/notes-client/src/openapi.d.ts b/clients/notes-client/src/openapi.d.ts index b561c217e..ddd192ae1 100644 --- a/clients/notes-client/src/openapi.d.ts +++ b/clients/notes-client/src/openapi.d.ts @@ -836,6 +836,7 @@ declare namespace Paths { } } + export interface OperationMethods { /** * createNote - createNote @@ -1148,6 +1149,7 @@ export interface PathsDictionary { export type Client = OpenAPIClient + export type ContextType = Components.Schemas.ContextType; export type CreatedByType = Components.Schemas.CreatedByType; export type Entity = Components.Schemas.Entity; diff --git a/clients/pricing-tier-client/src/openapi.d.ts b/clients/pricing-tier-client/src/openapi.d.ts index 766099622..fc2731743 100644 --- a/clients/pricing-tier-client/src/openapi.d.ts +++ b/clients/pricing-tier-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/purpose-client/src/openapi.d.ts b/clients/purpose-client/src/openapi.d.ts index 2a387f5c8..8a206de84 100644 --- a/clients/purpose-client/src/openapi.d.ts +++ b/clients/purpose-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/sandbox-client/src/openapi.d.ts b/clients/sandbox-client/src/openapi.d.ts index ad8ac521e..fb1b94e96 100644 --- a/clients/sandbox-client/src/openapi.d.ts +++ b/clients/sandbox-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/clients/submission-client/src/openapi.d.ts b/clients/submission-client/src/openapi.d.ts index a91a3d0c6..423f7dfc2 100644 --- a/clients/submission-client/src/openapi.d.ts +++ b/clients/submission-client/src/openapi.d.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/cli/README.md b/packages/cli/README.md index 5e26458c1..9b4dffea0 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -29,7 +29,7 @@ npm install -g @epilot/cli ``` -epilot v0.1.112 — CLI for epilot APIs +epilot v0.1.146 — CLI for epilot APIs USAGE epilot [params...] [flags] diff --git a/packages/cli/definitions/access-token.json b/packages/cli/definitions/access-token.json index 0bf18366b..9d1169a66 100644 --- a/packages/cli/definitions/access-token.json +++ b/packages/cli/definitions/access-token.json @@ -597,6 +597,12 @@ "description": "When true, the issued token may only perform read-only actions. Any action guarded by permissions that is not read-only (i.e. not a view/export/download action) is denied, regardless of the roles the token carries.", "example": true }, + "Anonymize": { + "type": "boolean", + "default": false, + "description": "Forces PII anonymization on all entity data returned to this token. Cannot be disabled by the token bearer. See entity-api anonymized responses.", + "example": true + }, "AccessTokenParameters": { "type": "object", "additionalProperties": false, @@ -618,6 +624,9 @@ }, "read_only": { "$ref": "#/components/schemas/ReadOnly" + }, + "anonymize": { + "$ref": "#/components/schemas/Anonymize" } }, "required": [ @@ -692,6 +701,9 @@ }, "read_only": { "$ref": "#/components/schemas/ReadOnly" + }, + "anonymize": { + "$ref": "#/components/schemas/Anonymize" } }, "required": [ @@ -719,6 +731,9 @@ }, "read_only": { "$ref": "#/components/schemas/ReadOnly" + }, + "anonymize": { + "$ref": "#/components/schemas/Anonymize" } }, "required": [ @@ -782,6 +797,9 @@ "read_only": { "$ref": "#/components/schemas/ReadOnly" }, + "anonymize": { + "$ref": "#/components/schemas/Anonymize" + }, "last_used": { "type": "string", "format": "date", diff --git a/packages/cli/definitions/ai-agents.json b/packages/cli/definitions/ai-agents.json index 3152f38bb..d47d96ae5 100644 --- a/packages/cli/definitions/ai-agents.json +++ b/packages/cli/definitions/ai-agents.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "version": "3.0.0", - "title": "AI Agents API - OpenAPI 3.0", + "title": "AI Agents API", "description": "API for configuring and invoking AI agents in epilot platform", "termsOfService": "https://epilot.cloud/agb", "contact": { @@ -29,6 +29,14 @@ { "name": "Agent Execution", "description": "Execute AI agents and manage executions" + }, + { + "name": "Chat", + "description": "Streaming chat with AI agents" + }, + { + "name": "Conversations", + "description": "Manage conversation history" } ], "paths": { @@ -37,7 +45,7 @@ "tags": [ "Agents Configuration" ], - "summary": "Create Agent definition", + "summary": "createAgent", "description": "Creates a new custom agent. System skills cannot be created via this endpoint.", "operationId": "createAgent", "security": [ @@ -82,7 +90,7 @@ "tags": [ "Agents Configuration" ], - "summary": "List all agent configurations", + "summary": "listAgents", "description": "Lists agents from both system skills and custom agents.\nUse query parameters to filter by source, availability, or entity schema.\n", "operationId": "listAgents", "parameters": [ @@ -140,7 +148,7 @@ "tags": [ "Agents Configuration" ], - "summary": "Get the agent configuration by ID", + "summary": "getAgentById", "description": "Retrieves an agent by ID. Supports both:\n- System skill IDs (prefixed): \"skill:email-categorizer\"\n- Custom agent IDs (UUID): \"0336a235-9417-4dd8-894c-fe81285bba75\"\n", "operationId": "getAgentById", "security": [ @@ -185,7 +193,7 @@ "tags": [ "Agents Configuration" ], - "summary": "Update the agent configuration by ID", + "summary": "updateAgentById", "description": "Updates a custom agent. System skills cannot be updated via this endpoint.", "operationId": "updateAgentById", "security": [ @@ -240,7 +248,7 @@ "tags": [ "Agents Configuration" ], - "summary": "Delete the agent configuration by ID", + "summary": "deleteAgentById", "description": "Deletes a custom agent. System skills cannot be deleted via this endpoint.", "security": [ { @@ -280,7 +288,7 @@ "tags": [ "Agent Execution" ], - "summary": "Execute an agent", + "summary": "executeAgent", "description": "Executes an agent (system skill or custom agent).\nSupports both:\n- System skill IDs (prefixed): \"skill:email-categorizer\"\n- Custom agent IDs (UUID): \"0336a235-9417-4dd8-894c-fe81285bba75\"\n", "operationId": "executeAgent", "security": [ @@ -342,12 +350,80 @@ } } }, + "/v1/agents/{agent_id}/execute/stream": { + "post": { + "tags": [ + "Agent Execution" + ], + "summary": "executeAgentStream", + "description": "Executes an agent with real-time streaming of tokens and tool events.\nReturns Server-Sent Events (SSE) stream with token-by-token output,\ntool call progress, and completion status.\n\nUnlike the async `/execute` endpoint which returns immediately with\nan execution_id for polling, this endpoint streams all events in real-time.\n\nSupports both:\n- System skill IDs (prefixed): \"skill:email-categorizer\"\n- Custom agent IDs (UUID): \"0336a235-9417-4dd8-894c-fe81285bba75\"\n", + "operationId": "executeAgentStream", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "agent_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/AgentId" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecuteAgentRequest" + } + } + } + }, + "responses": { + "200": { + "description": "SSE stream of execution events", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/StreamEvent" + } + } + } + }, + "400": { + "description": "Error while executing the agent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Agent not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, "/v1/executions": { "get": { "tags": [ "Agent Execution" ], - "summary": "List executions", + "summary": "listExecutions", + "description": "List executions", "operationId": "listExecutions", "security": [ { @@ -410,7 +486,8 @@ "tags": [ "Agent Execution" ], - "summary": "Get execution by ID", + "summary": "getExecution", + "description": "Get execution by ID", "operationId": "getExecution", "security": [ { @@ -455,7 +532,8 @@ "tags": [ "Agent Execution" ], - "summary": "Cancel execution", + "summary": "cancelExecution", + "description": "Cancel execution", "operationId": "cancelExecution", "security": [ { @@ -512,7 +590,7 @@ "tags": [ "Agent Execution" ], - "summary": "Get execution trace/iterations", + "summary": "getExecutionTrace", "description": "Returns the step-by-step reasoning and tool calls for ReAct mode executions. Returns empty iterations array for direct mode executions.", "operationId": "getExecutionTrace", "security": [ @@ -555,12 +633,136 @@ } } }, + "/v1/executions/{execution_id}/feedback": { + "get": { + "tags": [ + "Agent Execution" + ], + "summary": "Get execution feedback", + "description": "Returns the authenticated user's feedback for an execution.", + "operationId": "getExecutionFeedback", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "execution_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Feedback state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionFeedbackResponse" + } + } + } + }, + "404": { + "description": "Execution not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "tags": [ + "Agent Execution" + ], + "summary": "Submit execution feedback", + "description": "Upserts thumbs up/down feedback and mirrors it to the execution's Langfuse trace.", + "operationId": "putExecutionFeedback", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "execution_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutExecutionFeedbackRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Feedback recorded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionFeedbackResponse" + } + } + } + }, + "400": { + "description": "Invalid feedback", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Execution not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Execution cannot be rated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, "/v1/executions/{execution_id}/approve": { "post": { "tags": [ "Agent Execution" ], - "summary": "Approve pending action", + "summary": "approveExecution", "description": "Approves a pending tool action when execution is in waiting_approval status", "operationId": "approveExecution", "security": [ @@ -627,7 +829,7 @@ "tags": [ "Agent Execution" ], - "summary": "Reject pending action", + "summary": "rejectExecution", "description": "Rejects a pending tool action when execution is in waiting_approval status", "operationId": "rejectExecution", "security": [ @@ -689,65 +891,411 @@ } } } - } - }, - "components": { - "securitySchemes": { - "EpilotAuth": { - "type": "http", - "scheme": "bearer", - "description": "Authorization header with epilot OAuth2 bearer token. Supports RFC8725 for JWT validation.", - "bearerFormat": "JWT" - } }, - "schemas": { - "AgentId": { - "type": "string", - "description": "Agent identifier. Can be either:\n- System skill ID (prefixed): \"skill:email-categorizer\", \"skill:email-labeler\"\n- Custom agent UUID: \"0336a235-9417-4dd8-894c-fe81285bba75\"\n", - "example": "skill:email-categorizer" - }, - "CreateAgentRequest": { - "type": "object", - "required": [ - "name", - "system_prompt", - "execution_pattern" + "/v1/executions/{execution_id}/stream": { + "get": { + "tags": [ + "Agent Execution" ], - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 100, - "example": "Email Reply Generator" - }, - "description": { - "type": "string", - "maxLength": 1000 - }, - "category": { - "$ref": "#/components/schemas/SkillCategory" - }, - "icon": { - "type": "string", - "example": "mail-reply" - }, - "system_prompt": { - "type": "string", - "minLength": 1, - "description": "Core LLM instructions" + "summary": "streamExecution", + "description": "Reconnects to an execution's event stream after approval. Replays missed events from event log and continues streaming if execution is still running.", + "operationId": "streamExecution", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "execution_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } }, - "tools": { - "type": "array", - "items": { - "type": "string" + { + "name": "from_sequence", + "in": "query", + "schema": { + "type": "integer" }, - "description": "Tool IDs this agent can use", - "example": [ - "entity.search", - "message.draft" - ] + "description": "Resume from this event sequence number (for reconnection)" + } + ], + "responses": { + "200": { + "description": "SSE stream of execution events", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/StreamEvent" + } + } + } }, - "model_config": { + "404": { + "description": "Execution not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/chat": { + "post": { + "tags": [ + "Chat" + ], + "summary": "chat", + "description": "Initiates a streaming chat session with an AI agent. Supports server-side conversation memory via conversationId or client-provided history via clientHistory.", + "operationId": "chat", + "security": [ + { + "EpilotAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatRequest" + } + } + } + }, + "responses": { + "200": { + "description": "SSE stream of chat events", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/StreamEvent" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Agent not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/conversations": { + "get": { + "tags": [ + "Conversations" + ], + "summary": "listConversations", + "description": "Lists conversations for the authenticated user, sorted by most recent.", + "operationId": "listConversations", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "agent_id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter by agent ID" + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20 + }, + "description": "Maximum number of conversations to return" + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Pagination cursor" + } + ], + "responses": { + "200": { + "description": "List of conversations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListConversationsResponse" + } + } + } + }, + "400": { + "description": "Error fetching conversations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/conversations/{conversation_id}": { + "get": { + "tags": [ + "Conversations" + ], + "summary": "getConversation", + "description": "Retrieves a conversation and its message history.", + "operationId": "getConversation", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "message_limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100 + }, + "description": "Maximum number of messages to return" + } + ], + "responses": { + "200": { + "description": "Conversation with messages", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationWithMessages" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Conversations" + ], + "summary": "deleteConversation", + "description": "Deletes a conversation and all its messages.", + "operationId": "deleteConversation", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Conversation deleted" + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/conversations/{conversation_id}/feedback": { + "post": { + "tags": [ + "Conversations" + ], + "summary": "submitConversationFeedback", + "description": "Records a thumbs up/down (with optional comment) for the assistant turn identified by its Langfuse trace id. The rating is persisted on the message and mirrored to Langfuse as a trace score.\n", + "operationId": "submitConversationFeedback", + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitFeedbackRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Feedback recorded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "feedback": { + "$ref": "#/components/schemas/MessageFeedback" + } + } + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Conversation or message not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "description": "Authorization header with epilot OAuth2 bearer token. Supports RFC8725 for JWT validation.", + "bearerFormat": "JWT" + } + }, + "schemas": { + "AgentId": { + "type": "string", + "description": "Agent identifier. Can be either:\n- System skill ID (prefixed): \"skill:email-categorizer\", \"skill:email-labeler\"\n- Custom agent UUID: \"0336a235-9417-4dd8-894c-fe81285bba75\"\n", + "example": "skill:email-categorizer" + }, + "CreateAgentRequest": { + "type": "object", + "required": [ + "name", + "system_prompt", + "execution_pattern" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "example": "Email Reply Generator" + }, + "description": { + "type": "string", + "maxLength": 1000 + }, + "category": { + "$ref": "#/components/schemas/SkillCategory" + }, + "icon": { + "type": "string", + "example": "mail-reply" + }, + "system_prompt": { + "type": "string", + "minLength": 1, + "description": "Core LLM instructions" + }, + "tools": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tool IDs this agent can use", + "example": [ + "entity.search", + "message.draft" + ] + }, + "model_config": { "$ref": "#/components/schemas/ModelConfig" }, "max_iterations": { @@ -946,6 +1494,20 @@ "reason": { "type": "string", "description": "Optional reason for approval" + }, + "approved_action_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "For batch approval - list of action IDs to approve. If not provided, all actions are approved." + }, + "rejected_action_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "For batch approval - list of action IDs to reject. Actions not in approved_action_ids are implicitly rejected." } } }, @@ -1098,6 +1660,98 @@ "type": "string", "format": "date-time", "nullable": true + }, + "iterations": { + "type": "array", + "description": "Slim, labels-only step-progress projection of the ReAct iterations (empty for direct mode). Exposes only index, tool, status, and timestamp — raw thought, action input, and observation are intentionally excluded.", + "items": { + "$ref": "#/components/schemas/ExecutionIterationProjection" + } + } + } + }, + "ExecutionIterationProjection": { + "type": "object", + "properties": { + "index": { + "type": "integer" + }, + "tool": { + "type": "string", + "nullable": true, + "description": "Tool id the iteration invoked, or null for a non-tool step" + }, + "status": { + "type": "string", + "enum": [ + "running", + "completed" + ] + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } + }, + "ExecutionFeedback": { + "type": "object", + "required": [ + "rating", + "user_id", + "submitted_at" + ], + "properties": { + "rating": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "comment": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "submitted_at": { + "type": "string", + "format": "date-time" + } + } + }, + "ExecutionFeedbackResponse": { + "type": "object", + "required": [ + "feedback" + ], + "properties": { + "feedback": { + "allOf": [ + { + "$ref": "#/components/schemas/ExecutionFeedback" + } + ], + "nullable": true + } + } + }, + "PutExecutionFeedbackRequest": { + "type": "object", + "required": [ + "rating" + ], + "properties": { + "rating": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "comment": { + "type": "string" } } }, @@ -1223,8 +1877,107 @@ "duration_ms": { "type": "integer" }, - "iteration_count": { + "iteration_count": { + "type": "integer" + } + } + }, + "AgentExecutionEventStatus": { + "type": "string", + "description": "Lifecycle status carried on an agent execution event.\n\nDeliberately narrower than `ExecutionStatus`, which is the stored status of\nthe execution row: `pending` and `running` both surface as `started`, and\n`cancelled` surfaces as `failed`. Consumers must not assume the two\nvocabularies are interchangeable.\n", + "enum": [ + "started", + "completed", + "failed", + "approval_required" + ] + }, + "AgentExecutionEvent": { + "type": "object", + "description": "Detail payload of the `agent-execution-status-update` event, published on\nthe AI Agents event bus with source `ai-agents`.\n\n`execution_context` and `input` are present on every status, so a consumer\ncan tell whether an execution is theirs, and what it belonged to, from the\nterminal event alone — without a secondary lookup.\n", + "required": [ + "executionId", + "agentId", + "orgId", + "execution_status" + ], + "properties": { + "executionId": { + "type": "string", + "description": "The agent execution this event reports on" + }, + "agentId": { + "$ref": "#/components/schemas/AgentId" + }, + "orgId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "execution_status": { + "$ref": "#/components/schemas/AgentExecutionEventStatus" + }, + "execution_context": { + "$ref": "#/components/schemas/ExecutionContext" + }, + "input": { + "type": "object", + "additionalProperties": true, + "description": "The execution input, as supplied by the caller of the execute endpoint" + }, + "output": { + "type": "object", + "additionalProperties": true, + "description": "Present on `completed` — the execution result" + }, + "error": { + "type": "object", + "description": "Present on `failed`", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + }, + "stack": { + "type": "string" + } + } + }, + "duration": { + "type": "integer", + "description": "Wall-clock duration in milliseconds, present on `completed`" + }, + "iterations": { + "type": "integer" + }, + "iteration": { "type": "integer" + }, + "message": { + "type": "string", + "description": "Present on `approval_required` — what is awaiting approval" + }, + "toolCalls": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "args": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "timestamp": { + "type": "string", + "format": "date-time" } } }, @@ -1286,7 +2039,8 @@ "apply", "send", "link", - "unlink" + "unlink", + "batch_approval" ], "description": "Type of action being previewed" }, @@ -1414,7 +2168,7 @@ "type": "string", "enum": [ "entity", - "email", + "message", "taxonomy", "rag", "workflow" @@ -1462,9 +2216,10 @@ "enum": [ "flows", "copilot", + "portals", "all" ], - "description": "Where the skill/agent is available:\n- flows: Available in workflow automations\n- copilot: Available as a sub-agent in copilot\n- all: Available everywhere\n" + "description": "Where the skill/agent is available:\n- flows: Available in workflow automations\n- copilot: Available as a sub-agent in copilot\n- portals: Available in end-user self-service portals\n- all: Available everywhere\n" }, "ExecutionContext": { "type": "string", @@ -1517,9 +2272,10 @@ "taxonomy", "taxonomy-classification", "shared-inbox", - "label" + "label", + "matching-criteria" ], - "description": "Base types:\n- text: Text input field\n- textarea: Multi-line text input field\n- number: Numeric input field\n- boolean: Toggle switch\n- select: Dropdown selection (requires enum array)\n\nCustom types (domain-specific):\n- entity-schema: Entity schema selector (fetches from Entity API)\n- entity-attribute: Entity attribute selector (requires dependsOn)\n- entity-id: Entity picker (search and select entities)\n- taxonomy: Taxonomy selector (fetches from Taxonomy API)\n- taxonomy-classification: Classification selector (requires dependsOn)\n- shared-inbox: Shared inbox selector (fetches from Email Settings API)\n" + "description": "Base types:\n- text: Text input field\n- textarea: Multi-line text input field\n- number: Numeric input field\n- boolean: Toggle switch\n- select: Dropdown selection (requires enum array)\n\nCustom types (domain-specific):\n- entity-schema: Entity schema selector (fetches from Entity API)\n- entity-attribute: Entity attribute selector (requires dependsOn)\n- entity-id: Entity picker (search and select entities)\n- taxonomy: Taxonomy selector (fetches from Taxonomy API)\n- taxonomy-classification: Classification selector (requires dependsOn)\n- shared-inbox: Shared inbox selector (fetches from Email Settings API)\n- matching-criteria: Criteria editor for mapping compared fields between two contexts or entities\n" }, "InputParameterDefinition": { "type": "object", @@ -1605,6 +2361,11 @@ "type": "string" }, "description": "Filter to specific attribute types (for entity-attribute)" + }, + "hidden": { + "type": "boolean", + "default": false, + "description": "Whether to hide the parameter from the UI" } } }, @@ -1689,6 +2450,18 @@ "type": "integer", "default": 4096, "maximum": 8192 + }, + "thinking": { + "type": "boolean", + "default": false, + "description": "Enable extended thinking/reasoning for the model" + }, + "thinking_budget": { + "type": "integer", + "default": 10000, + "minimum": 1000, + "maximum": 50000, + "description": "Token budget for extended thinking (only used when thinking is enabled)" } } }, @@ -1705,6 +2478,385 @@ "type": "object" } } + }, + "ChatRequest": { + "type": "object", + "required": [ + "agentId", + "message" + ], + "properties": { + "agentId": { + "type": "string", + "description": "Agent ID to chat with" + }, + "message": { + "type": "string", + "description": "User message" + }, + "conversationId": { + "type": "string", + "format": "uuid", + "description": "Conversation ID for server-side memory. If provided, loads history from DynamoDB." + }, + "clientHistory": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChatMessage" + }, + "description": "Client-provided message history (overrides server-side memory)" + }, + "context": { + "type": "object", + "properties": { + "entityId": { + "type": "string" + }, + "customData": { + "type": "object", + "additionalProperties": true + } + } + }, + "streaming": { + "$ref": "#/components/schemas/StreamingOptions" + } + } + }, + "ChatMessage": { + "type": "object", + "required": [ + "role", + "content" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "user", + "assistant", + "tool", + "system" + ] + }, + "content": { + "type": "string" + }, + "tool_calls": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "input": { + "type": "object" + }, + "output": { + "type": "string" + } + } + } + } + } + }, + "StreamingOptions": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "updates", + "messages" + ], + "default": "updates", + "description": "Stream mode - updates for step-based, messages for token-based" + }, + "streamTokens": { + "type": "boolean", + "default": false, + "description": "Enable token-level streaming" + }, + "includeMetadata": { + "type": "boolean", + "default": false, + "description": "Include metadata events" + } + } + }, + "StreamEvent": { + "type": "object", + "description": "Server-Sent Event for streaming responses", + "properties": { + "type": { + "type": "string", + "enum": [ + "token", + "agent_step", + "tool_call", + "tool_result", + "complete", + "error", + "metadata", + "needs_approval" + ] + }, + "content": { + "type": "string", + "description": "Token content (for token events)" + }, + "index": { + "type": "integer", + "description": "Token index (for token events)" + }, + "tool": { + "type": "string", + "description": "Tool name (for tool events)" + }, + "input": { + "type": "object", + "description": "Tool input (for tool_call events)" + }, + "output": { + "type": "string", + "description": "Tool output (for tool_result events)" + }, + "callId": { + "type": "string", + "description": "Tool call ID" + }, + "result": { + "$ref": "#/components/schemas/ExecutionResult" + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "conversationId": { + "type": "string", + "format": "uuid", + "description": "Conversation ID (included in complete events)" + } + } + }, + "ExecutionResult": { + "type": "object", + "properties": { + "response": { + "type": "string" + }, + "structured_output": { + "type": "object" + }, + "status": { + "type": "string", + "enum": [ + "completed", + "failed", + "max_iterations", + "rejected" + ] + }, + "metrics": { + "$ref": "#/components/schemas/ExecutionMetrics" + } + } + }, + "ConversationItem": { + "type": "object", + "properties": { + "conversation_id": { + "type": "string", + "format": "uuid" + }, + "user_id": { + "type": "string" + }, + "agent_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "message_count": { + "type": "integer" + }, + "last_message": { + "type": "string", + "description": "Preview of the last message" + }, + "last_message_at": { + "type": "string", + "format": "date-time" + }, + "context": { + "type": "object", + "properties": { + "entityId": { + "type": "string" + }, + "customData": { + "type": "object" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "MessageItem": { + "type": "object", + "properties": { + "conversation_id": { + "type": "string", + "format": "uuid" + }, + "role": { + "type": "string", + "enum": [ + "user", + "assistant", + "tool", + "system" + ] + }, + "content": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "tool_calls": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "input": { + "type": "object" + }, + "output": { + "type": "string" + } + } + } + }, + "token_count": { + "type": "integer" + }, + "trace_id": { + "type": "string", + "format": "uuid", + "description": "Langfuse trace id for the turn (assistant messages only)" + }, + "feedback": { + "$ref": "#/components/schemas/MessageFeedback" + } + } + }, + "MessageFeedback": { + "type": "object", + "required": [ + "rating", + "user_id", + "submitted_at" + ], + "properties": { + "rating": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "comment": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "submitted_at": { + "type": "string", + "format": "date-time" + } + } + }, + "SubmitFeedbackRequest": { + "type": "object", + "required": [ + "trace_id", + "rating" + ], + "properties": { + "trace_id": { + "type": "string", + "format": "uuid", + "description": "Langfuse trace id of the assistant turn being rated" + }, + "rating": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "comment": { + "type": "string", + "description": "Optional free-text feedback" + } + } + }, + "ListConversationsResponse": { + "type": "object", + "properties": { + "conversations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConversationItem" + } + }, + "next_cursor": { + "type": "string", + "nullable": true + } + } + }, + "ConversationWithMessages": { + "type": "object", + "properties": { + "conversation": { + "$ref": "#/components/schemas/ConversationItem" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageItem" + } + } + } } } } diff --git a/packages/cli/definitions/app.json b/packages/cli/definitions/app.json index e8b95fadd..115b19552 100644 --- a/packages/cli/definitions/app.json +++ b/packages/cli/definitions/app.json @@ -1150,6 +1150,9 @@ "description": "Target version to create" } ], + "requestBody": { + "$ref": "#/components/requestBodies/CloneVersionRequest" + }, "responses": { "201": { "description": "New version created successfully", @@ -1438,6 +1441,70 @@ } } }, + "/v1/app/{appId}/options/resolve": { + "post": { + "summary": "resolveOptions", + "description": "Resolve the effective app-level options of an installation, including decrypted sensitive values (secrets). This endpoint accepts epilot internal-auth tokens exclusively (internal service calls, e.g. automation-workers or the customer portal API) — no user token, API key, or app token can call it, regardless of roles. Every call is audit-logged with the caller identity.", + "operationId": "resolveOptions", + "tags": [ + "App Installation" + ], + "parameters": [ + { + "name": "appId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Narrow the response to these option keys" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Effective options with resolved values", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Options" + } + } + } + } + } + } + }, + "403": { + "description": "Caller is not authenticated with an internal-auth token" + }, + "404": { + "description": "App installation not found" + } + } + } + }, "/v1/app-events": { "post": { "summary": "ingestEvent", @@ -1501,6 +1568,18 @@ "type": "string" }, "description": "Path to forward to the proxy target" + }, + { + "name": "query", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "object", + "additionalProperties": true + }, + "description": "Free-form query parameters, forwarded unchanged to the proxy target" } ], "get": { @@ -1565,6 +1644,105 @@ "description": "Bad gateway - proxy target unreachable or error" } } + }, + "put": { + "summary": "publicProxyPut", + "operationId": "publicProxyPut", + "description": "Forward a PUT request to a registered proxy target from a public-facing component", + "tags": [ + "App Proxy" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Proxied response from the target API" + }, + "403": { + "description": "Forbidden - app not installed or no public component uses this proxy" + }, + "404": { + "description": "Proxy target not found" + }, + "502": { + "description": "Bad gateway - proxy target unreachable or error" + } + } + }, + "patch": { + "summary": "publicProxyPatch", + "operationId": "publicProxyPatch", + "description": "Forward a PATCH request to a registered proxy target from a public-facing component", + "tags": [ + "App Proxy" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Proxied response from the target API" + }, + "403": { + "description": "Forbidden - app not installed or no public component uses this proxy" + }, + "404": { + "description": "Proxy target not found" + }, + "502": { + "description": "Bad gateway - proxy target unreachable or error" + } + } + }, + "delete": { + "summary": "publicProxyDelete", + "operationId": "publicProxyDelete", + "description": "Forward a DELETE request to a registered proxy target from a public-facing component", + "tags": [ + "App Proxy" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "responses": { + "200": { + "description": "Proxied response from the target API" + }, + "403": { + "description": "Forbidden - app not installed or no public component uses this proxy" + }, + "404": { + "description": "Proxy target not found" + }, + "502": { + "description": "Bad gateway - proxy target unreachable or error" + } + } } } }, @@ -1713,6 +1891,42 @@ }, "grants": { "$ref": "#/components/schemas/Grants" + }, + "functions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FunctionDefinition" + }, + "description": "Replaces the full set of server-side functions for this version" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Options" + }, + "description": "Replaces the full set of app-level option declarations for this version.\nOption keys must be unique app-wide; `secret`-touching options are always\nsensitive and must not declare `sensitive: false`.\n" + }, + "changelog": { + "type": "string", + "maxLength": 250, + "description": "Short description of what changed in this version" + } + } + } + } + } + }, + "CloneVersionRequest": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "changelog": { + "type": "string", + "maxLength": 250, + "description": "Short description of what the new version will change" } } } @@ -1820,7 +2034,7 @@ }, "Options": { "type": "object", - "description": "Options for the component configuration", + "description": "An option declaration — a setting the installing org fills in. Declared at app level.", "required": [ "key", "type" @@ -1866,6 +2080,25 @@ "object" ], "description": "The type of this option. `object` declares a structured value whose fields are listed\nunder `fields`. Combine with `repeatable: true` to express a list of these objects.\n" + }, + "sensitive": { + "type": "boolean", + "description": "Write-only, server-side only value — like a sensitive environment variable\n(app-level options only). Sensitive values are never serialized in any response;\nthey are resolvable only via server-side channels (API proxy injection, function\nruns, the internal options/resolve endpoint). Non-sensitive values\nare readable wherever the app runs, including the end-customer browser (journey and\nportal runtime). Forced to true for `secret`-touching options. Default: false.\n" + }, + "configured": { + "type": "boolean", + "readOnly": true, + "description": "Serialized instead of `value` for sensitive options (secrets): true when a\nvalue is stored.\n" + }, + "lifted": { + "type": "boolean", + "readOnly": true, + "description": "Compatibility marker: this option is declared at app level and folded back\ninto the component's options so existing consumers (journey runtime, portal\nblocks, older services) keep working unchanged. New consumers should read\napp-level options from the installation's `options` array instead.\n" + }, + "value_updated_at": { + "type": "string", + "readOnly": true, + "description": "Timestamp of the last value change. Only returned for sensitive options\n(alongside `configured`) so installers can see when a secret was last rotated.\n" } } }, @@ -1930,7 +2163,7 @@ "properties": { "component_id": { "type": "string", - "description": "ID of the component these values are for" + "description": "ID of the component these values are for. Use the sentinel `$app` for\napp-level option values (options declared at the manifest top level).\n" }, "options": { "type": "array", @@ -1970,10 +2203,88 @@ "CUSTOM_CAPABILITY", "EXTERNAL_PRODUCT_CATALOG", "CUSTOM_PAGE", - "API_PROXY" + "API_PROXY", + "EXTERNAL_VALUES", + "APP_FUNCTION" ], "description": "Type of app component" }, + "FunctionDefinition": { + "type": "object", + "description": "A named server-side function belonging to the app. Runs in the epilot code-execution sandbox with an installation-scoped app token. Functions with a schedule are executed automatically once per installation.\n", + "required": [ + "name", + "type", + "code" + ], + "properties": { + "name": { + "type": "string", + "description": "Unique function name within the app", + "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" + }, + "type": { + "type": "string", + "enum": [ + "workflow", + "scheduled" + ], + "description": "Where the function can be used. `workflow` functions are referenced by CUSTOM_FLOW_ACTION components (type `function`) and run with entity context. `scheduled` functions run automatically per installation on their cron schedule.\n" + }, + "label": { + "allOf": [ + { + "description": "Human-readable display name of the function, shown to installing organizations (e.g. in the scheduled-functions summary)\n" + }, + { + "$ref": "#/components/schemas/TranslatedString" + } + ] + }, + "description": { + "allOf": [ + { + "description": "Description of the function" + }, + { + "$ref": "#/components/schemas/TranslatedString" + } + ] + }, + "code": { + "type": "string", + "maxLength": 307200, + "description": "JavaScript code to execute. Must declare a top-level `async function handler(input, context)`. Maximum size: 300KB (hard limit). Security restrictions: eval() and Function() constructor are not allowed.\n" + }, + "schedule": { + "type": "string", + "maxLength": 100, + "description": "Cron trigger for the function, executed once per installation. Standard 5-field cron expression (e.g. \"0 3 * * *\") or rate expression (e.g. \"rate(30 minutes)\"). Minimum interval: 15 minutes. Scheduled runs are limited to 60 seconds.\n", + "example": "rate(30 minutes)" + }, + "schedule_timezone": { + "type": "string", + "description": "IANA timezone the cron expression is evaluated in", + "default": "Europe/Berlin" + }, + "schedule_overlap": { + "type": "string", + "enum": [ + "skip" + ], + "default": "skip", + "description": "Behavior when the previous scheduled run is still active" + }, + "secrets": { + "type": "array", + "items": { + "type": "string" + }, + "deprecated": true, + "description": "DEPRECATED and ignored — functions receive the app's full effective option keyspace (sensitive values included) via input.app_options. Kept for manifest compatibility.\n" + } + } + }, "Author": { "type": "object", "required": [ @@ -2060,6 +2371,13 @@ }, "description": "List of options for the app component" }, + "uses_options": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Keys of app-level options this component uses. Narrows which options are\nfolded back into `options` for legacy consumers (e.g. which values a journey\nblock receives in its public args) — a scoping hint, never a security\nboundary. Stamped automatically by the component-options migration; may also\nbe declared by the app developer.\n" + }, "surfaces": { "type": "object" } @@ -2083,7 +2401,8 @@ "CUSTOM_CAPABILITY": "#/components/schemas/CustomCapabilityComponent", "EXTERNAL_PRODUCT_CATALOG": "#/components/schemas/ExternalProductCatalogComponent", "CUSTOM_PAGE": "#/components/schemas/CustomPageComponent", - "API_PROXY": "#/components/schemas/ApiProxyComponent" + "API_PROXY": "#/components/schemas/ApiProxyComponent", + "EXTERNAL_VALUES": "#/components/schemas/ExternalValuesComponent" } }, "oneOf": [ @@ -2113,6 +2432,9 @@ }, { "$ref": "#/components/schemas/ApiProxyComponent" + }, + { + "$ref": "#/components/schemas/ExternalValuesComponent" } ] } @@ -2136,6 +2458,25 @@ } } }, + "ExternalValuesComponent": { + "type": "object", + "description": "Exposes typed values resolved from an external system at runtime (e.g. a meter reading prediction). Consumers such as validation rules reference a hook and one of its results by id. Resolution is performed by the external-values-api on behalf of authenticated epilot 360 users and portal end customers.\n", + "required": [ + "component_type", + "configuration" + ], + "properties": { + "component_type": { + "type": "string", + "enum": [ + "EXTERNAL_VALUES" + ] + }, + "configuration": { + "$ref": "#/components/schemas/ExternalValuesConfig" + } + } + }, "CustomCapabilityComponent": { "type": "object", "required": [ @@ -2157,7 +2498,8 @@ "type": "string", "enum": [ "tab", - "group" + "group", + "widget" ] }, "allowed_schemas": { @@ -2386,7 +2728,7 @@ } ] }, - "SandboxCustomActionConfig": { + "FunctionRefCustomActionConfig": { "allOf": [ { "$ref": "#/components/schemas/BaseCustomActionConfig" @@ -2397,41 +2739,37 @@ "type": { "type": "string", "enum": [ - "sandbox" + "function" ] }, - "sandbox_settings": { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "JavaScript code to execute for the sandbox action. Maximum size: 300KB (hard limit). Code is stored as raw JavaScript and will be syntax-validated on save. Security restrictions: eval() and Function() constructor are not allowed.\n", - "maxLength": 307200 - } - } + "function_name": { + "type": "string", + "description": "Name of a `workflow`-type function of the same app version. The component provides the org-facing contract (name, options, config surface); the referenced function provides the code.\n" } }, "required": [ - "type" + "type", + "function_name" ] } ] }, "CustomFlowConfig": { "type": "object", + "description": "Configuration of a flow action component. `external_integration` calls an external HTTP endpoint; `function` runs a workflow-type app function in the epilot sandbox.\n", "oneOf": [ { "$ref": "#/components/schemas/ExternalIntegrationCustomActionConfig" }, { - "$ref": "#/components/schemas/SandboxCustomActionConfig" + "$ref": "#/components/schemas/FunctionRefCustomActionConfig" } ], "discriminator": { "propertyName": "type", "mapping": { "external_integration": "#/components/schemas/ExternalIntegrationCustomActionConfig", - "sandbox": "#/components/schemas/SandboxCustomActionConfig" + "function": "#/components/schemas/FunctionRefCustomActionConfig" } } }, @@ -2651,7 +2989,10 @@ "dataExport": "#/components/schemas/PortalExtensionHookDataExport", "costDataRetrieval": "#/components/schemas/PortalExtensionHookCostDataRetrieval", "meterReadingPlausibilityCheck": "#/components/schemas/PortalExtensionHookMeterReadingPlausibilityCheck", - "visualizationMetadata": "#/components/schemas/PortalExtensionHookVisualizationMetadata" + "visualizationMetadata": "#/components/schemas/PortalExtensionHookVisualizationMetadata", + "changeEmail": "#/components/schemas/PortalExtensionHookChangeEmail", + "changePassword": "#/components/schemas/PortalExtensionHookChangePassword", + "deleteAccount": "#/components/schemas/PortalExtensionHookDeleteAccount" } }, "oneOf": [ @@ -2678,6 +3019,15 @@ }, { "$ref": "#/components/schemas/PortalExtensionHookVisualizationMetadata" + }, + { + "$ref": "#/components/schemas/PortalExtensionHookChangeEmail" + }, + { + "$ref": "#/components/schemas/PortalExtensionHookChangePassword" + }, + { + "$ref": "#/components/schemas/PortalExtensionHookDeleteAccount" } ] } @@ -3074,7 +3424,8 @@ "PT15M", "PT1H", "P1D", - "P1M" + "P1M", + "P1Y" ] } }, @@ -3185,7 +3536,8 @@ "PT15M", "PT1H", "P1D", - "P1M" + "P1M", + "P1Y" ] } }, @@ -3364,7 +3716,7 @@ "additionalProperties": false }, "PortalExtensionHookVisualizationMetadata": { - "description": "Hook that returns runtime metadata describing how a visualization (consumption / price / cost chart) should be rendered for a given portal context (meter, contract, etc). It is invoked by the portal before fetching data, with the same context the data hook would receive, so that the discovery shape can vary per meter/contract. The expected response to the call is:\n - 200 with a JSON body of shape:\n {\n \"type_options\": [\n { \"id\": \"ht\", \"label\": { \"en\": \"High tariff\" }, \"aggregation_group\": \"consumption\", \"statistical_method\": \"sum\", \"unit\": \"kWh\", \"color\": \"primary\", \"precision\": 2 },\n ...\n ],\n \"intervals\": [\"PT15M\", \"PT1H\", \"P1D\", \"P1M\"],\n \"data_range\": { \"from\": \"2024-01-01T00:00:00Z\", \"to\": \"2026-05-01T00:00:00Z\" }\n }\n Each type option carries its own `statistical_method`, which describes the method already applied to that type's data and dictates the chart shape: `sum` is rendered as a bar chart; `min`, `average`, and `max` are rendered as a line chart. A single visualization can therefore mix bar-shaped types with line-shaped types. Defaults to `sum` when omitted.\n Each type option may also customize its rendering: `color` picks a Spark palette color (`primary`, `slate`, `mauve`, `orange`, `red`, `tomato`, `amber`, `green`, `blue`) used to draw the type's series; `precision` sets the number of decimal places to show for that type's values (axis labels, tooltips, summaries). Both are optional — the consumer falls back to its own defaults when they are omitted.\n `aggregation_group` controls how types within a group are visually combined (depends on the per-type `statistical_method`):\n - bar chart (`sum`): same-group types are stacked into a single bar (e.g. ht/nt summed into total consumption); different-group types render side-by-side.\n - line chart (`min` / `average` / `max`): same-group types are rendered as an area chart; different-group types render as separate lines.\n All fields are optional; the consumer falls back to its defaults for whatever the hook does not return.\nThe portal looks up this hook implicitly per extension (one `visualizationMetadata` hook per extension) — there is no need for a data-retrieval hook to reference it explicitly.\n", + "description": "Hook that returns runtime metadata describing how a visualization (consumption / price / cost chart) should be rendered for a given portal context (meter, contract, etc). It is invoked by the portal before fetching data, with the same context the data hook would receive, so that the discovery shape can vary per meter/contract. The expected response to the call is:\n - 200 with a JSON body of shape:\n {\n \"type_options\": [\n { \"id\": \"ht\", \"label\": { \"en\": \"High tariff\" }, \"aggregation_group\": \"consumption\", \"statistical_method\": \"sum\", \"unit\": \"kWh\", \"color\": \"primary\", \"precision\": 2 },\n ...\n ],\n \"intervals\": [\"PT15M\", \"PT1H\", \"P1D\", \"P1M\", \"P1Y\"],\n \"data_range\": { \"from\": \"2024-01-01T00:00:00Z\", \"to\": \"2026-05-01T00:00:00Z\" }\n }\n Each type option carries its own `statistical_method`, which describes the method already applied to that type's data and dictates the chart shape: `sum` is rendered as a bar chart; `min`, `average`, and `max` are rendered as a line chart. A single visualization can therefore mix bar-shaped types with line-shaped types. Defaults to `sum` when omitted.\n Each type option may also customize its rendering: `color` picks a Spark palette color (`primary`, `slate`, `mauve`, `orange`, `red`, `tomato`, `amber`, `green`, `blue`) used to draw the type's series; `precision` sets the number of decimal places to show for that type's values (axis labels, tooltips, summaries). Both are optional — the consumer falls back to its own defaults when they are omitted.\n `aggregation_group` controls how types within a group are visually combined (depends on the per-type `statistical_method`):\n - bar chart (`sum`): same-group types are stacked into a single bar (e.g. ht/nt summed into total consumption); different-group types render side-by-side.\n - line chart (`min` / `average` / `max`): same-group types are rendered as an area chart; different-group types render as separate lines.\n All fields are optional; the consumer falls back to its defaults for whatever the hook does not return.\nThe portal looks up this hook implicitly per extension (one `visualizationMetadata` hook per extension) — there is no need for a data-retrieval hook to reference it explicitly.\n", "type": "object", "properties": { "id": { @@ -3475,22 +3827,376 @@ "type": { "type": "string", "enum": [ - "costDataRetrieval" + "costDataRetrieval" + ] + }, + "intervals": { + "type": "array", + "deprecated": true, + "description": "Deprecated. Prefer declaring a sibling `visualizationMetadata` hook on the same extension and returning `intervals` from its response — that way the supported intervals can vary per meter/contract.\nIntervals supported by the API. If omitted, it is assumed that all intervals are supported.\n", + "items": { + "type": "string", + "enum": [ + "PT15M", + "PT1H", + "P1D", + "P1M", + "P1Y" + ] + } + }, + "auth": { + "$ref": "#/components/schemas/PortalExtensionAuthBlock" + }, + "call": { + "type": "object", + "properties": { + "method": { + "type": "string", + "description": "HTTP method to use for the call", + "default": "GET" + }, + "url": { + "type": "string", + "description": "URL to call. Supports variable interpolation." + }, + "params": { + "type": "object", + "description": "Parameters to append to the URL. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "headers": { + "type": "object", + "description": "Headers to use. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "body": { + "type": "object", + "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", + "additionalProperties": true + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + "resolved": { + "type": "object", + "properties": { + "data_path": { + "type": "string", + "description": "Optional path to the data (array) in the response. If omitted, the data is assumed to be on the top level." + }, + "dataPath": { + "type": "string", + "deprecated": true, + "description": "Deprecated. Use `data_path` instead." + }, + "error_message_path": { + "type": "string", + "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", + "example": "error.message" + } + }, + "additionalProperties": false + }, + "use_static_ips": { + "type": "boolean", + "deprecated": true, + "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", + "default": false + }, + "secure_proxy": { + "$ref": "#/components/schemas/PortalExtensionSecureProxy" + } + }, + "required": [ + "id", + "type", + "call" + ], + "additionalProperties": false + }, + "PortalExtensionHookChangeEmail": { + "description": "Hook that replaces the built-in change email functionality for portal users. When configured, the portal does not run its own change email flow. Instead, this hook makes an HTTP call to the third-party system, which is expected to handle the email change.\nThe `change_mode` controls what the portal does after the call:\n - `asynchronous`: The third-party system takes the email change over entirely (most likely by sending the user instructions to confirm the new email address). The portal does not change the login email itself.\n - `synchronous`: The third-party system applies the email change immediately. The portal waits for a successful (2xx) response and then also changes the portal user's login email right away, without sending a confirmation email. The user has to sign in again with the new email address afterwards.\n\nThe expected response http status code to the call is:\n - 2xx if the request was accepted\n - non-2xx if the request failed (optionally with a human-readable message resolved via `resolved.error_message_path`)\n", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Identifier of the hook. Should not change between updates." + }, + "name": { + "$ref": "#/components/schemas/TranslatedString" + }, + "type": { + "type": "string", + "enum": [ + "changeEmail" + ] + }, + "change_mode": { + "type": "string", + "enum": [ + "synchronous", + "asynchronous" + ], + "description": "Controls how the email change is handled once the third-party system accepted it. `asynchronous` hands the email change over entirely to the third-party system and the portal does not change the login email itself. `synchronous` waits for the third-party system to respond and then changes the portal user's login email immediately, without a confirmation email.\n", + "default": "asynchronous" + }, + "require_password_confirmation": { + "type": "boolean", + "description": "Whether the portal user must confirm their current password before the change email request is handed over to the third-party system. When true, the portal collects and verifies the current password before calling the hook. Skipped for portal users whose identity is managed by an identity provider: an SSO login has no password to confirm. When no password is confirmed and `change_mode` is `synchronous`, the re-created login gets a random password and the portal user signs in through their identity provider, or sets a new password via the email code flow.\n", + "default": true + }, + "explanation": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Explanation of the functionality shown to the end user.", + "example": "You will receive an email with instructions to confirm your new email address." + } + }, + "additionalProperties": { + "type": "string", + "description": "Explanation of the functionality in language denoted by ISO 3166-1 alpha-2 code." + }, + "required": [ + "en" + ], + "description": "Optional explanation shown to the user in the change email confirmation dialog." + }, + "auth": { + "$ref": "#/components/schemas/PortalExtensionAuthBlock" + }, + "call": { + "type": "object", + "properties": { + "method": { + "type": "string", + "description": "HTTP method to use for the call", + "default": "POST" + }, + "url": { + "type": "string", + "description": "URL to call. Supports variable interpolation." + }, + "params": { + "type": "object", + "description": "Parameters to append to the URL. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "headers": { + "type": "object", + "description": "Headers to use. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "body": { + "type": "object", + "description": "Optional JSON body to use for the call. Defaults to an object with the requested new email and portal user context, e.g. `{\"new_email\": \"...\", \"old_email\": \"...\", \"portal_user_id\": \"...\"}`. The requested new email is available as `{{Input.new_email}}` and the current account email as `{{Input.old_email}}`. Supports variable interpolation." + } + }, + "required": [ + "url", + "headers" + ], + "additionalProperties": false + }, + "resolved": { + "type": "object", + "properties": { + "error_message_path": { + "type": "string", + "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", + "example": "error.message" + } + }, + "additionalProperties": false + }, + "secure_proxy": { + "$ref": "#/components/schemas/PortalExtensionSecureProxy" + } + }, + "required": [ + "id", + "type", + "call" + ], + "additionalProperties": false + }, + "PortalExtensionHookChangePassword": { + "description": "Hook that replaces the built-in change password functionality for portal users. When configured, the portal does not change the user's password itself. Instead, this hook makes an HTTP call to the third-party system, which is expected to handle the password change (most likely by sending the user instructions to complete the process).\nThe expected response http status code to the call is:\n - 2xx if the request was accepted\n - non-2xx if the request failed (optionally with a human-readable message resolved via `resolved.error_message_path`)\n", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Identifier of the hook. Should not change between updates." + }, + "name": { + "$ref": "#/components/schemas/TranslatedString" + }, + "type": { + "type": "string", + "enum": [ + "changePassword" + ] + }, + "require_new_password": { + "type": "boolean", + "description": "Whether the portal user must provide a new password. When false, the portal only asks the user to confirm (showing the configured explanation) and no new password is collected; the third-party system is expected to handle the password change. When true, the portal collects a new password and passes it to the third-party system as `{{Input.new_password}}`.\n", + "default": false + }, + "explanation": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Explanation of the functionality shown to the end user.", + "example": "You will receive an email with instructions to reset your password." + } + }, + "additionalProperties": { + "type": "string", + "description": "Explanation of the functionality in language denoted by ISO 3166-1 alpha-2 code." + }, + "required": [ + "en" + ], + "description": "Optional explanation shown to the user in the change password confirmation dialog." + }, + "auth": { + "$ref": "#/components/schemas/PortalExtensionAuthBlock" + }, + "call": { + "type": "object", + "properties": { + "method": { + "type": "string", + "description": "HTTP method to use for the call", + "default": "POST" + }, + "url": { + "type": "string", + "description": "URL to call. Supports variable interpolation." + }, + "params": { + "type": "object", + "description": "Parameters to append to the URL. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "headers": { + "type": "object", + "description": "Headers to use. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "body": { + "type": "object", + "description": "Optional JSON body to use for the call. Defaults to an object with portal user context (and the new password as `{{Input.new_password}}` when `require_new_password` is true). Supports variable interpolation." + } + }, + "required": [ + "url", + "headers" + ], + "additionalProperties": false + }, + "resolved": { + "type": "object", + "properties": { + "error_message_path": { + "type": "string", + "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", + "example": "error.message" + } + }, + "additionalProperties": false + }, + "secure_proxy": { + "$ref": "#/components/schemas/PortalExtensionSecureProxy" + } + }, + "required": [ + "id", + "type", + "call" + ], + "additionalProperties": false + }, + "PortalExtensionHookDeleteAccount": { + "description": "Hook that replaces the built-in delete account functionality for portal users. When configured, the portal does not delete the user itself. Instead, this hook makes an HTTP call to the third-party system, which is expected to handle the deletion.\nThe `deletion_mode` controls what the portal does after the call:\n - `synchronous`: The third-party system deletes the user immediately. The portal waits for a successful (2xx) response and then also deletes the corresponding epilot Cognito user.\n - `asynchronous`: The third-party system handles deletion out-of-band. The portal does not delete anything immediately; cleanup is expected to happen later (e.g. via the user deletion API or webhooks).\n\nThe optional `delete_contact` additionally deletes the contact related to the portal user, once the portal user itself was deleted (`synchronous` mode only).\n\nThe expected response http status code to the call is:\n - 2xx if the request was accepted\n - non-2xx if the request failed (optionally with a human-readable message resolved via `resolved.error_message_path`)\n", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Identifier of the hook. Should not change between updates." + }, + "name": { + "$ref": "#/components/schemas/TranslatedString" + }, + "type": { + "type": "string", + "enum": [ + "deleteAccount" ] }, - "intervals": { - "type": "array", - "deprecated": true, - "description": "Deprecated. Prefer declaring a sibling `visualizationMetadata` hook on the same extension and returning `intervals` from its response — that way the supported intervals can vary per meter/contract.\nIntervals supported by the API. If omitted, it is assumed that all intervals are supported.\n", - "items": { + "deletion_mode": { + "type": "string", + "enum": [ + "synchronous", + "asynchronous" + ], + "description": "Controls how the account deletion is handled. `synchronous` waits for the third-party system to respond and then also deletes the epilot user. `asynchronous` hands the deletion over entirely to the third-party system and the portal does not delete anything immediately.\n", + "default": "synchronous" + }, + "delete_contact": { + "type": "string", + "enum": [ + "none", + "soft", + "hard" + ], + "description": "Opt-in deletion of the contact related to the portal user, applied after the portal user itself was deleted. `none` (default) leaves the contact untouched. `soft` deletes the contact, so it can still be restored from the trash. `hard` permanently purges the contact. Only applied in `synchronous` deletion mode, as `asynchronous` mode hands the cleanup over to the third-party system.\n", + "default": "none" + }, + "explanation": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Explanation of the functionality shown to the end user.", + "example": "Your account deletion will be processed by our system. This may take a few days." + } + }, + "additionalProperties": { "type": "string", - "enum": [ - "PT15M", - "PT1H", - "P1D", - "P1M" - ] - } + "description": "Explanation of the functionality in language denoted by ISO 3166-1 alpha-2 code." + }, + "required": [ + "en" + ], + "description": "Optional explanation shown to the user in the delete account confirmation dialog." }, "auth": { "$ref": "#/components/schemas/PortalExtensionAuthBlock" @@ -3501,7 +4207,7 @@ "method": { "type": "string", "description": "HTTP method to use for the call", - "default": "GET" + "default": "POST" }, "url": { "type": "string", @@ -3525,27 +4231,18 @@ }, "body": { "type": "object", - "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", - "additionalProperties": true + "description": "Optional JSON body to use for the call. Defaults to an object with portal user context, e.g. `{\"portal_user_id\": \"...\", \"email\": \"...\"}`. Supports variable interpolation." } }, "required": [ - "url" + "url", + "headers" ], "additionalProperties": false }, "resolved": { "type": "object", "properties": { - "data_path": { - "type": "string", - "description": "Optional path to the data (array) in the response. If omitted, the data is assumed to be on the top level." - }, - "dataPath": { - "type": "string", - "deprecated": true, - "description": "Deprecated. Use `data_path` instead." - }, "error_message_path": { "type": "string", "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", @@ -3554,12 +4251,6 @@ }, "additionalProperties": false }, - "use_static_ips": { - "type": "boolean", - "deprecated": true, - "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", - "default": false - }, "secure_proxy": { "$ref": "#/components/schemas/PortalExtensionSecureProxy" } @@ -4113,6 +4804,39 @@ } } }, + "InternalReview": { + "description": "Review entry including app ownership info, used by internal admin endpoints", + "allOf": [ + { + "$ref": "#/components/schemas/Review" + }, + { + "type": "object", + "properties": { + "app_id": { + "type": "string", + "description": "ID of the app configuration", + "readOnly": true + }, + "org_id": { + "type": "string", + "description": "Organization ID of the app owner", + "readOnly": true + }, + "reviewed_by": { + "type": "string", + "description": "Identity of the internal reviewer who approved the review", + "readOnly": true + }, + "reviewed_at": { + "type": "string", + "description": "Timestamp of the approval", + "readOnly": true + } + } + } + ] + }, "ConfigurationMetadata": { "description": "Basic metadata about your app configuration which does not get versioned", "type": "object", @@ -4258,6 +4982,22 @@ }, "minItems": 0 }, + "functions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FunctionDefinition" + }, + "minItems": 0, + "description": "Server-side functions of the app, including scheduled functions" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Options" + }, + "minItems": 0, + "description": "App-level option declarations (with sensitivity) of this version" + }, "visibility": { "type": "string", "enum": [ @@ -4358,8 +5098,7 @@ }, "target": { "type": "string", - "format": "uri", - "description": "Base URL of the target API. Must be HTTPS.", + "description": "Base URL of the target API. Must be HTTPS. May reference component options via {{option_key}} interpolation (resolved from decrypted values), e.g. \"https://{{cluster}}.example.com/api\" for a per-installation host. The fully-resolved URL is validated at request time (SSRF protection).", "pattern": "^https://" }, "auth_type": { @@ -4367,10 +5106,11 @@ "enum": [ "header", "bearer", + "basic", "oauth2", "none" ], - "description": "Authentication strategy" + "description": "Authentication strategy. 'basic' sends an HTTP Basic Authorization header built server-side from the 'username' and 'password' component options." }, "auth_header": { "type": "string", @@ -4381,6 +5121,211 @@ "format": "uri", "description": "OAuth2 token endpoint URL", "pattern": "^https://" + }, + "headers": { + "type": "object", + "description": "Additional request headers injected server-side on every proxied request. Values may reference component options via {{option_key}} interpolation (resolved from decrypted secrets), so credentials are never exposed to the client. Applied in addition to auth_type.", + "additionalProperties": { + "type": "string" + } + } + } + }, + "ExternalValuesConfig": { + "type": "object", + "description": "Configuration for an EXTERNAL_VALUES component", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "array", + "description": "Hooks exposed by this component. Hook ids must be unique within the component.", + "items": { + "$ref": "#/components/schemas/ExternalValuesHook" + } + } + } + }, + "ExternalValuesHook": { + "type": "object", + "description": "One external call that returns one or more typed results. Interpolated templates (Liquid) have access to `Input`, `Context`, `Consumer`, `Options`, `Env` and — for `result` templates — `Response` and `AuthResponse`.\n", + "required": [ + "id", + "name", + "call", + "result" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]{1,100}$", + "description": "Stable identifier; consumers (e.g. validation rules) reference it. Renaming breaks references." + }, + "name": { + "$ref": "#/components/schemas/TranslatedString" + }, + "description": { + "$ref": "#/components/schemas/TranslatedString" + }, + "auth_type": { + "type": "string", + "enum": [ + "none", + "header", + "bearer", + "hmac" + ], + "default": "none", + "description": "Static request authentication applied to the call.\n- `none`: no static authentication.\n- `header`: sends `auth_secret` in header `auth_header` (default `X-API-Key`).\n- `bearer`: sends `Authorization: Bearer `.\n- `hmac`: signs the request per Standard Webhooks (headers `webhook-id`,\n `webhook-timestamp`, `webhook-signature` = `v1,` over\n `..`) using `auth_secret` as a `whsec_`-prefixed\n base64 secret, exactly as epilot webhooks do.\n\n`auth_secret` is required when `auth_type` is `header`, `bearer` or `hmac`.\nCombine with `auth` (a pre-call that fetches a token) when the target needs both.\n" + }, + "auth_header": { + "type": "string", + "description": "Header name used when `auth_type` is `header`. Defaults to `X-API-Key`." + }, + "auth_secret": { + "type": "string", + "description": "Liquid template resolving to the secret, e.g. \"{{Options.api_key}}\". Must reference an option declared with `type: secret`; literals are rejected. Required when `auth_type` is `header`, `bearer` or `hmac`.", + "example": "{{Options.api_key}}" + }, + "auth": { + "$ref": "#/components/schemas/PortalExtensionAuthBlock" + }, + "call": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH" + ], + "default": "POST", + "description": "HTTP method to use for the call" + }, + "url": { + "type": "string", + "description": "URL to call. Supports variable interpolation." + }, + "headers": { + "type": "object", + "description": "Headers to send with the call. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + } + }, + "params": { + "type": "object", + "description": "Query parameters to append to the URL. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object", + "description": "JSON body to send. Values support variable interpolation. When omitted, `{ Input, Context, Consumer }` is sent as-is.", + "additionalProperties": true + } + } + }, + "result": { + "type": "array", + "minItems": 1, + "description": "Typed results extracted from the response. Result ids must be unique within the hook.", + "items": { + "$ref": "#/components/schemas/ExternalValuesResult" + } + }, + "timeout_ms": { + "type": "integer", + "minimum": 500, + "maximum": 5000, + "default": 3000, + "description": "Timeout for the external call in milliseconds." + }, + "on_unavailable": { + "type": "string", + "enum": [ + "skip", + "block" + ], + "default": "skip", + "description": "Behaviour when the external system is unavailable (timeout, network or upstream error). `skip`: consumers ignore conditions depending on this hook. `block`: consumers treat the dependent action as not allowed." + }, + "cache": { + "type": "object", + "description": "Response caching. Off when omitted.", + "required": [ + "ttl_seconds" + ], + "properties": { + "ttl_seconds": { + "type": "integer", + "minimum": 0, + "maximum": 3600, + "description": "Time to live in seconds. 0 disables caching." + }, + "key": { + "type": "string", + "description": "Liquid template for the cache key. Defaults to a hash of org, app, hook, `Input` and `Context`." + } + } + }, + "use_static_ips": { + "type": "boolean", + "description": "If set, requests are sent from epilot's static egress IPs. Mutually exclusive with secure_proxy." + }, + "secure_proxy": { + "$ref": "#/components/schemas/PortalExtensionSecureProxy" + } + } + }, + "ExternalValuesResult": { + "type": "object", + "description": "A single typed value extracted from the hook response. Exactly one of `template`, `path` or `jsonata` must be set.\n", + "required": [ + "id", + "type", + "name" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]{1,100}$", + "description": "Stable identifier of the result within the hook. Renaming breaks references." + }, + "type": { + "type": "string", + "enum": [ + "number", + "text", + "date", + "boolean" + ], + "description": "Type the extracted value is coerced to." + }, + "name": { + "$ref": "#/components/schemas/TranslatedString" + }, + "template": { + "type": "string", + "description": "Liquid template over `Response` etc.; output coerced to `type`.", + "example": "{{Response.value}}" + }, + "path": { + "type": "string", + "description": "Dot path into the response body, e.g. \"data.limit\".", + "example": "data.limit" + }, + "jsonata": { + "type": "string", + "description": "JSONata expression over `{ Response, Input, Context, Options, Env }`.", + "example": "$sum(Response.items.amount)" } } }, @@ -4459,6 +5404,11 @@ "description": "Name of the app", "readOnly": true }, + "icon_url": { + "type": "string", + "description": "Public URL of the app logo, if one has been uploaded", + "readOnly": true + }, "option_values": { "type": "array", "items": { @@ -4475,6 +5425,22 @@ "minLength": 0, "readOnly": true }, + "functions": { + "description": "Server-side functions of the installed version, including scheduled functions", + "type": "array", + "items": { + "$ref": "#/components/schemas/FunctionDefinition" + }, + "readOnly": true + }, + "options": { + "description": "Effective app-level options of the installation: declared top-level options plus\ncomponent-level options lifted to the app keyspace, each with its configured\n`value`. Values of sensitive options (secrets) are never included — they carry\n`configured` and `value_updated_at` instead.\n", + "type": "array", + "items": { + "$ref": "#/components/schemas/Options" + }, + "readOnly": true + }, "installed_version": { "type": "string", "description": "Version of the app that is installed", @@ -4582,6 +5548,22 @@ }, "minItems": 0 }, + "functions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FunctionDefinition" + }, + "minItems": 0, + "description": "Server-side functions of the app, including scheduled functions" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Options" + }, + "minItems": 0, + "description": "App-level option declarations (with sensitivity) of this version" + }, "is_beta": { "type": "boolean", "description": "Flag to indicate if the app is in beta.", @@ -4832,6 +5814,11 @@ "correlation_id": { "type": "string", "description": "Filter by correlation ID for tracing" + }, + "search": { + "type": "string", + "maxLength": 200, + "description": "Case-insensitive substring match over event details and component id" } } }, @@ -4882,6 +5869,11 @@ "minimum": 1, "maximum": 1000, "default": 100 + }, + "before": { + "type": "string", + "format": "date-time", + "description": "Cursor: only return events strictly older than this timestamp. Use the timestamp of the last received event to fetch the next page; more efficient than increasing page/page_size.\n" } } }, diff --git a/packages/cli/definitions/automation.json b/packages/cli/definitions/automation.json index 511b1d18a..92e0254c3 100644 --- a/packages/cli/definitions/automation.json +++ b/packages/cli/definitions/automation.json @@ -79,6 +79,15 @@ "example": "wfABCDEFGH" } }, + { + "name": "trigger_event_name", + "in": "query", + "description": "Filter by Event Catalog event name. Returns only automations that have an event_catalog trigger for this event.", + "schema": { + "type": "string", + "example": "CustomerRequestSubmitted" + } + }, { "name": "include_flows", "in": "query", @@ -621,6 +630,42 @@ } } }, + "/v1/automation/executions:search": { + "post": { + "operationId": "searchExecutions", + "summary": "searchExecutions", + "description": "Search automation executions of an entity with cursor-based pagination.\nReturns pages of up to 100 executions, newest first.\nPrefer this over GET /v1/automation/executions, which returns the full\nexecution history in a single response.\n", + "tags": [ + "executions" + ], + "requestBody": { + "description": "Search parameters", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchExecutionsReq" + } + } + } + }, + "responses": { + "200": { + "description": "A page of automation executions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchExecutionsResp" + } + } + } + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + } + } + } + }, "/v1/automation/executions/bulk-jobs": { "post": { "operationId": "bulkTriggerExecutions", @@ -1213,6 +1258,9 @@ }, { "$ref": "#/components/schemas/FlowsTrigger" + }, + { + "$ref": "#/components/schemas/EventCatalogTrigger" } ] }, @@ -1259,6 +1307,9 @@ }, { "$ref": "#/components/schemas/ReplyEmailAction" + }, + { + "$ref": "#/components/schemas/AssignEntityAction" } ] }, @@ -1305,6 +1356,9 @@ }, { "$ref": "#/components/schemas/ReplyEmailActionConfig" + }, + { + "$ref": "#/components/schemas/AssignEntityActionConfig" } ] }, @@ -1815,6 +1869,10 @@ "type": "string", "description": "Schema of target entity" }, + "use_uniqueness_criteria": { + "type": "boolean", + "description": "Resolve this action's target (contact/account) via the organization's global uniqueness criteria instead of per-action identifiers (target_unique / Unique switches). Forwarded to entity mapping by automation-workers.\n" + }, "target_unique": { "type": "array", "description": "Unique key for target entity (see upsertEntity of Entity API)", @@ -1854,12 +1912,168 @@ "items": { "type": "string" } + }, + "graph_context": { + "type": "array", + "description": "Multi-hop graph queries (executed via entity-api's `POST /v1/entity:graph`), each\nmerging every traversed node into the mapping's source context under its own node id,\nso `mapping_attributes` (e.g. `_copy` / `_template`) can reference it alongside the\nmain entity, e.g. `{ \"_copy\": \"order.line_items\" }`.\n\n`seed.entity_id` and any node `filter[].value` may contain `{{handlebars}}`\nplaceholders (e.g. `{{trigger.entity._id}}`) that are resolved at execution time\nagainst the mapping's source context. Resolution and graph execution happen in\nentity-mapping-api; this API only accepts, validates and passes through this field.\n", + "items": { + "$ref": "#/components/schemas/GraphContextEntry" + } } }, "required": [ "target_schema" ] }, + "GraphContextEntry": { + "type": "object", + "description": "One multi-hop graph query to run against entity-api's `POST /v1/entity:graph`. Every node\nin `graph.nodes` is merged into the mapping's source context under its own `id`, so\nlisting multiple nodes here costs one entity-api call, not one per node. A node's value\noverwrites any existing source-context key of the same name (the source entity, a 1-hop\nrelation, or another graph node).\n", + "required": [ + "seed", + "graph" + ], + "properties": { + "seed": { + "$ref": "#/components/schemas/GraphSeed" + }, + "graph": { + "$ref": "#/components/schemas/GraphDefinition" + } + } + }, + "GraphSeed": { + "type": "object", + "required": [ + "entity_id", + "node_id" + ], + "properties": { + "entity_id": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "description": "The node ID in the graph definition that corresponds to the seed entity", + "example": "contact" + } + } + }, + "GraphDefinition": { + "type": "object", + "required": [ + "nodes", + "edges" + ], + "properties": { + "nodes": { + "type": "array", + "description": "List of node definitions in the graph", + "items": { + "$ref": "#/components/schemas/GraphNode" + } + }, + "edges": { + "type": "array", + "description": "List of edge definitions connecting nodes", + "items": { + "$ref": "#/components/schemas/GraphEdge" + } + } + } + }, + "GraphNode": { + "type": "object", + "required": [ + "id", + "schema" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this node in the graph definition", + "example": "contact" + }, + "schema": { + "type": "string", + "description": "Entity schema slug for this node", + "example": "contact" + }, + "cardinality": { + "type": "string", + "enum": [ + "one", + "many" + ], + "description": "\"one\": this node resolves to a single entity. \"many\" (default if unset): this node\nresolves to an array of entities. The seed node always returns a single entity\nregardless of this setting.\n", + "example": "one" + }, + "fields": { + "type": "array", + "description": "Optional entity fields to include in the hydrated response for this node.", + "items": { + "type": "string" + } + }, + "filter": { + "type": "array", + "description": "Narrows this node's traversal results to entities matching every filter (AND\nsemantics). Useful for disambiguating among multiple entities reachable via the same\ngraph edge.\n", + "items": { + "$ref": "#/components/schemas/GraphNodeFilter" + } + } + } + }, + "GraphNodeFilter": { + "type": "object", + "description": "Entities are included in this node's result only if `attribute` exactly equals the literal `value`.", + "required": [ + "attribute", + "value" + ], + "properties": { + "attribute": { + "type": "string", + "description": "Entity attribute name to match against.", + "example": "order_number" + }, + "value": { + "description": "Literal value the attribute must exactly equal for the entity to be included in this node's result. Supports `{{handlebars}}` placeholders resolved against sourceContext when given as a string.", + "oneOf": [ + { + "type": "string", + "nullable": true + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ], + "example": "OR-113" + } + } + }, + "GraphEdge": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string", + "description": "Source node ID", + "example": "contact" + }, + "to": { + "type": "string", + "description": "Target node ID", + "example": "billing_account" + } + } + }, "MappingConfigRef": { "type": "object", "properties": { @@ -2187,13 +2401,225 @@ }, "add": { "type": "array", - "description": "Assignees to add to the thread", + "description": "Assignees to add to the thread. For even_distribution this may contain group IDs (prefixed \"group_\") whose members are candidates.", + "items": { + "type": "string" + } + }, + "assignment_type": { + "type": "string", + "description": "How assignees in `add` are resolved.\n- direct: assign exactly the users in `add` (default).\n- even_distribution: treat `add` as a candidate pool (users and/or groups)\n and assign the least-loaded available agent.\n- sequential: reserved for future use.\n", + "enum": [ + "direct", + "even_distribution", + "sequential" + ], + "default": "direct" + }, + "only_available_users": { + "type": "boolean", + "description": "Candidate condition for even_distribution: when true, skip users who\nare currently absent (out-of-office). Opt-in; defaults to false.\n", + "default": false + }, + "hold_until_available": { + "type": "boolean", + "description": "Sub-option of only_available_users: when a day off (weekend / whole\nday off with a known return) is what empties the candidate pool, hold\nthe thread and retry when the soonest candidate is back — bounded to\n7 retries over 7 days — before applying the fallback. Defaults to\ntrue; set false to skip the hold and fall straight through to the\nfallback. Out-of-office / vacation and non-availability reasons never\nhold regardless.\n", + "default": true + }, + "match_user_skills": { + "type": "boolean", + "description": "Candidate condition for even_distribution: when true, only assign to\nusers whose skills (tags) match a label on the message. Opt-in;\ndefaults to false.\n", + "default": false + }, + "required_skill_categories": { + "type": "array", + "description": "Candidate condition (with match_user_skills): label families\n(taxonomy slugs) that must match. A user qualifies only if it shares\na label with the message within each of these categories. Leave empty\nto match on any label (flat OR across all categories).\n", "items": { "type": "string" } + }, + "skill_match_mode": { + "type": "string", + "description": "How strictly required skill categories are matched (applies with\nmatch_user_skills and required skill categories set).\n- require_all: hard match (default) — a user must match every required\n category the message has a label in.\n- prefer: soft match — prefer the best-matching users, relaxing to\n fewer categories only when no better match exists; a user matching\n no category is never eligible (the fallback then applies).\n", + "enum": [ + "require_all", + "prefer" + ], + "default": "require_all" + }, + "fallback": { + "type": "string", + "description": "What to do when smart assignment resolves no assignable candidate.\nApplies to even_distribution.\n- leave_unassigned: leave the thread unassigned (default).\n- assign_to_fallback: route to the users/groups in `fallback_assignees`.\n", + "enum": [ + "leave_unassigned", + "assign_to_fallback" + ], + "default": "leave_unassigned" + }, + "fallback_assignees": { + "type": "array", + "description": "Target user/group IDs (group IDs prefixed \"group_\") to assign when\n`fallback` is \"assign_to_fallback\".\n", + "items": { + "type": "string" + } + } + } + }, + "EntityAssignee": { + "type": "object", + "description": "A single assignee as stored in a user-relation attribute. Written through\nverbatim by the assign-entity worker. Note this object encoding differs\ndeliberately from AssignThreadConfig, which stores bare id strings —\neach matches what its own target accepts.\n", + "properties": { + "type": { + "type": "string", + "description": "Which kind of principal this assignee is. Required: it selects which\nof the identifier properties below applies, and forms part of the\nappend-mode de-duplication key.\n", + "enum": [ + "user", + "partner_user", + "partner_organization", + "group" + ] + }, + "user_id": { + "type": "string", + "description": "Set for user and partner_user assignees." + }, + "group_id": { + "type": "string", + "description": "Set for group assignees." + }, + "org_id": { + "type": "string", + "description": "Organization the assignee belongs to." + }, + "partner_id": { + "type": "string", + "description": "Set for partner_user and partner_organization assignees." + }, + "display_name": { + "type": "string", + "description": "Label snapshotted at configuration time; may go stale after a rename." + }, + "email": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "AssignEntityConfig": { + "type": "object", + "properties": { + "attribute": { + "type": "string", + "description": "Slug of the user-relation attribute on the triggering entity to write to." + }, + "assignees": { + "type": "array", + "description": "Assignees to write into `attribute`, in the order given.", + "items": { + "$ref": "#/components/schemas/EntityAssignee" + } + }, + "write_mode": { + "type": "string", + "description": "How `assignees` combine with the attribute's current value.\n- replace: overwrite the attribute with `assignees` (default).\n- append: union with the existing assignees, de-duplicated, existing\n entries first. Two assignees are duplicates when their `type` and\n `org_id` both match and the identifier for that type matches:\n `user_id` for user and partner_user, `group_id` for group,\n `partner_id` for partner_organization. Scoping by `org_id` keeps\n assignees from different organizations distinct even when their\n identifiers coincide.\n", + "enum": [ + "replace", + "append" + ], + "default": "replace" + }, + "assignment_type": { + "type": "string", + "description": "How assignees are resolved.\n- direct: assign exactly the assignees listed (default, and the only\n accepted value).\nReserved for a future routing strategy; widening this enum later is\nbackwards compatible and needs no migration of existing flows.\n", + "enum": [ + "direct" + ], + "default": "direct" + }, + "source": { + "type": "object", + "description": "Which entity to assign to. Omit for the triggering entity (the\ndefault, and the behaviour of every flow saved before this field\nexisted).\n- trigger: the entity that started the flow.\n- action: the entity produced by an earlier action, identified by\n that action's `flow_action_id`.\n", + "properties": { + "id": { + "type": "string", + "description": "The producing action's `flow_action_id` when origin is `action`, or the trigger id when origin is `trigger`. Note this is `flow_action_id` (stable across executions), not `id`.\n" + }, + "origin": { + "type": "string", + "enum": [ + "trigger", + "action" + ] + }, + "schema": { + "type": "string", + "description": "Entity schema recorded when the source was chosen, for display. The worker uses the schema on the resolved entity itself, since this value goes stale if the producing action's target changes.\n" + } + } + } + } + }, + "AssignEntityActionConfig": { + "allOf": [ + { + "$ref": "#/components/schemas/AutomationActionConfig" + }, + { + "type": "object", + "properties": { + "type": { + "enum": [ + "assign-entity" + ] + }, + "config": { + "$ref": "#/components/schemas/AssignEntityConfig" + } + } + } + ], + "example": { + "id": "25jga0-gkasl26-0asg-908sgaj2", + "name": "Assign Entity", + "type": "assign-entity", + "config": { + "attribute": "assigned_to", + "write_mode": "replace", + "assignment_type": "direct", + "assignees": [ + { + "type": "user", + "user_id": "90503", + "org_id": "66", + "display_name": "Jane Doe", + "email": "jane.doe@example.com" + } + ] } } }, + "AssignEntityAction": { + "allOf": [ + { + "$ref": "#/components/schemas/AutomationAction" + }, + { + "type": "object", + "properties": { + "type": { + "enum": [ + "assign-entity" + ] + }, + "config": { + "$ref": "#/components/schemas/AssignEntityConfig" + } + } + } + ] + }, "SendEmailActionConfig": { "allOf": [ { @@ -2405,10 +2831,7 @@ "language_code": { "type": "string", "description": "Language code for the email template", - "enum": [ - "de", - "en" - ] + "example": "de" }, "reply_mode": { "type": "string", @@ -2439,10 +2862,7 @@ }, "language_code": { "type": "string", - "enum": [ - "de", - "en" - ] + "example": "de" }, "notify_portal_user_only": { "type": "boolean", @@ -2459,6 +2879,15 @@ "description": "Pause automation execution after sending email to wait for a confirmation link to be clicked.\n\nThe email template should contain a confirmation link using the variable `{{confirmation_url}}`\n", "default": false }, + "wait_for_journey_submission": { + "type": "boolean", + "description": "Only relevant when this action runs from a workflow automation task. After the email is sent,\nthe workflow task waits for the journey referenced in the email template to be submitted and\nits submission automation to complete (AL-2521).\n\nThe email template should contain a journey link created with the generateJourneyLink variable.\n", + "default": false + }, + "journey_id": { + "type": "string", + "description": "Snapshot of the journey referenced by the selected email template's journey link; set by the\nbuilder UI. Display/validation only — correlation does not depend on it.\n" + }, "reply_to_sender": { "type": "boolean", "description": "When enabled, overrides the template's \"To\" field with the sender address of the triggering incoming email.\nThis is useful for auto-reply scenarios where you want to automatically respond to the person who sent the email.\nOnly works when the automation is triggered by a received email (received_email or new_email_thread triggers).\n", @@ -3236,10 +3665,12 @@ }, "originType": { "type": "string", + "description": "`event` reads the operand from the trigger's Event Catalog payload instead of an entity: `attribute` is then a dot path into the payload (e.g. `ticket.subject`). Only valid with `origin: trigger` on flows started by an EventCatalogTrigger.\n", "enum": [ "entity", "workflow", - "journey_block" + "journey_block", + "event" ] }, "schema": { @@ -3345,6 +3776,14 @@ "type": "string", "description": "Schedule Id which indicates the schedule of the actions inside the condition" }, + "evaluation_order": { + "type": "string", + "enum": [ + "AFTER_SCHEDULE", + "BEFORE_SCHEDULE" + ], + "description": "Determines how the condition and its schedule combine in time, when the condition block also has a schedule.\n\n- `AFTER_SCHEDULE` (default): the schedule is created first and the condition is evaluated when the schedule fires, against the data at that moment (\"wait, then check\").\n- `BEFORE_SCHEDULE`: the condition is evaluated at trigger time against the current data; only when it passes is the schedule created, and it is not re-evaluated when it fires (\"check now, then wait\"). A failing condition skips the block immediately.\n\nAn absent value is treated as `AFTER_SCHEDULE` for backwards compatibility." + }, "evaluationResult": { "type": "boolean", "description": "Result of the condition evaluation" @@ -3352,6 +3791,10 @@ "error_output": { "$ref": "#/components/schemas/ErrorOutput" }, + "allow_failure": { + "description": "Whether the execution should continue when processing the condition\nblock itself fails (e.g. its schedule cannot be computed). When true,\nthe block's actions are cancelled and the execution moves on to the\nnext action instead of stopping in a failed state.\n", + "type": "boolean" + }, "statements": { "type": "array", "items": { @@ -3517,12 +3960,18 @@ }, { "$ref": "#/components/schemas/TriggerEventMessaging" + }, + { + "$ref": "#/components/schemas/TriggerEventEventCatalog" } ] }, "workflow_context": { "$ref": "#/components/schemas/WorkflowExecutionContext" }, + "workflow_wait_context": { + "$ref": "#/components/schemas/WorkflowWaitContext" + }, "loops": { "type": "array", "description": "Loop scope definitions propagated from the flow onto the execution record. Each loop has an id and a source_path resolved against the trigger entity at execution time. Actions referencing a loop's id via their loop_id property run once per item in the resolved array.\n", @@ -3536,6 +3985,13 @@ "additionalProperties": { "$ref": "#/components/schemas/AutomationLoopState" } + }, + "chain": { + "type": "array", + "description": "Ordered automation flow ids in this execution's trigger chain (multi-automation loop prevention).", + "items": { + "type": "string" + } } }, "required": [ @@ -3583,6 +4039,13 @@ "_execution_chain": { "$ref": "#/components/schemas/ExecutionChain" }, + "_automation_chain": { + "type": "array", + "description": "[Internal] Ordered automation flow ids that led to the parent flow being started. The started execution appends its own flow id, so entity writes it performs carry the full chain and a loop running through a workflow can be detected. This is an internal property and should not be used by external consumers.\n", + "items": { + "type": "string" + } + }, "entity_contexts": { "type": "array", "description": "Additional entity contexts from the parent flow execution. Used when an automation is triggered from a workflow task to carry all flow contexts into the automation, not just the primary entity.\n", @@ -3600,6 +4063,31 @@ } } } + }, + "trigger_user_id": { + "type": "string", + "description": "Id of the user on whose behalf the workflow task triggered this automation — the caller of a user-initiated run, or the user whose task completion advanced the flow to the automation task. Action workers resolve it as the acting user for template variable resolution (user.*) and email thread assignment, matching the attribution of manually triggered executions. Absent when no human action led to the trigger (scheduler fires, journey-triggered flows).\n", + "example": "10006129" + } + } + }, + "WorkflowWaitContext": { + "type": "object", + "description": "Correlation stamped when the triggering submission entity carried workflow wait claims from a journey link (AL-2521). Consumed by svc-workflows to resume a task waiting on this journey submission.\n", + "required": [ + "workflow_execution_id", + "workflow_task_id" + ], + "properties": { + "workflow_execution_id": { + "type": "string" + }, + "workflow_task_id": { + "type": "string" + }, + "source": { + "type": "string", + "example": "journey_submission" } } }, @@ -3771,6 +4259,92 @@ "operation_type" ] }, + "TriggerEventEventCatalog": { + "type": "object", + "description": "Set on executions started by an Event Catalog event (see EventCatalogTrigger). The full event payload is not stored inline (it may be up to 256 KB) but by reference in `payload_ref`; automation workers hydrate it before every action.\n", + "properties": { + "type": { + "type": "string", + "enum": [ + "event_catalog" + ] + }, + "org_id": { + "type": "string", + "example": "123" + }, + "entity_id": { + "$ref": "#/components/schemas/EntityId" + }, + "entity_node_id": { + "type": "string", + "description": "Node id of the event's entity graph that was resolved to `entity_id`", + "example": "ticket" + }, + "event_id": { + "type": "string", + "description": "Event Catalog event id (ULID) of the published event", + "example": "01K3ZK8QZ7Y7Q2M8W1V3N4X5P6" + }, + "event_name": { + "type": "string", + "example": "CustomerRequestSubmitted" + }, + "event_version": { + "type": "string", + "description": "Version the payload was delivered in (the version pinned on the trigger)", + "example": "1.1" + }, + "published_version": { + "type": "string", + "description": "Version the event was originally published with, before downgrading to `event_version`", + "example": "1.2" + }, + "event_time": { + "type": "string", + "format": "date-time" + }, + "trigger_source_type": { + "type": "string", + "description": "How the event was triggered in the catalog (api | automation | operation | portal_user)", + "example": "operation" + }, + "trigger_source": { + "type": "string", + "description": "Opaque source reference from the catalog (e.g. activity id, automation execution id)" + }, + "payload_ref": { + "$ref": "#/components/schemas/TriggerEventPayloadRef" + } + }, + "required": [ + "type", + "org_id", + "entity_id", + "entity_node_id", + "event_id", + "event_name", + "event_version" + ] + }, + "TriggerEventPayloadRef": { + "type": "object", + "description": "S3 reference to the stored trigger event payload (`_downgrades` stripped, downgraded to the pinned version)", + "properties": { + "bucket": { + "type": "string", + "example": "automation-trigger-payloads-prod" + }, + "key": { + "type": "string", + "example": "trigger-payloads/123/6c8e5d9a-7f1b-4c2e-9d3a-0b1c2d3e4f5a.json" + } + }, + "required": [ + "bucket", + "key" + ] + }, "ApiCallerContext": { "type": "object", "additionalProperties": true, @@ -3866,6 +4440,56 @@ "results" ] }, + "SearchExecutionsReq": { + "type": "object", + "properties": { + "entity_id": { + "$ref": "#/components/schemas/EntityId" + }, + "include_flows": { + "description": "Include flow automations in the response", + "type": "boolean", + "default": false + }, + "size": { + "description": "Max number of executions to return per page", + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "cursor": { + "description": "Opaque cursor returned as next_cursor by a previous page.\nPass it to fetch the next page of results.\n", + "type": "string" + } + }, + "required": [ + "entity_id" + ] + }, + "SearchExecutionsResp": { + "type": "object", + "properties": { + "total": { + "description": "Number of executions in this page", + "type": "number" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationExecution" + } + }, + "next_cursor": { + "description": "Opaque cursor to fetch the next page of results.\nOnly present when more results are available.\n", + "type": "string" + } + }, + "required": [ + "total", + "results" + ] + }, "StartExecutionRequest": { "type": "object", "properties": { @@ -4632,7 +5256,12 @@ "updateEntity", "deleteEntity", "softDeleteEntity", - "restoreEntity" + "restoreEntity", + "relationsAdded", + "relationsRemoved", + "relationsSoftDeleted", + "relationsRestored", + "relationsDeleted" ] }, "EntitySearchFilter": { @@ -4838,6 +5467,76 @@ } ] }, + "EventCatalogTrigger": { + "description": "Starts the flow when an Event Catalog event is published for the organization. The execution runs in the context of one entity from the event's entity graph (`entity_node_id`), and the event payload is available to conditions and actions as the `event` variable context.\n", + "allOf": [ + { + "$ref": "#/components/schemas/AutomationTrigger" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "event_catalog" + ] + }, + "configuration": { + "type": "object", + "properties": { + "event_name": { + "type": "string", + "description": "Name of the Event Catalog event that starts this flow", + "example": "CustomerRequestSubmitted" + }, + "event_version": { + "type": "string", + "description": "Event version (MAJOR.MINOR) this trigger is pinned to. Payloads published in a newer version are downgraded to this version before the flow runs, so field references in conditions and actions stay stable.\n", + "example": "1.1" + }, + "entity_node_id": { + "type": "string", + "description": "Id of the cardinality-one node in the event's entity graph whose entity becomes the execution's main entity (the entity actions operate on).\n", + "example": "ticket" + }, + "entity_schema": { + "type": "string", + "description": "Schema of the entity behind `entity_node_id`, denormalized from the event definition at configuration time", + "example": "ticket" + }, + "ignore_automation_triggered": { + "type": "boolean", + "default": true, + "description": "When true (default), events that were emitted by an automation (`_trigger_source_type: automation`) do not start this flow. This prevents automation → event → automation chains unless explicitly enabled.\n" + } + }, + "required": [ + "event_name", + "event_version", + "entity_node_id", + "entity_schema" + ] + } + }, + "required": [ + "type", + "configuration" + ] + } + ], + "example": { + "id": "12d4f45a-1883-4841-a94c-5928cb338a94", + "type": "event_catalog", + "configuration": { + "event_name": "CustomerRequestSubmitted", + "event_version": "1.1", + "entity_node_id": "ticket", + "entity_schema": "ticket", + "ignore_automation_triggered": true + } + } + }, "TriggerCondition": { "type": "object", "properties": { diff --git a/packages/cli/definitions/billing.json b/packages/cli/definitions/billing.json index 969848c6b..6ced06877 100644 --- a/packages/cli/definitions/billing.json +++ b/packages/cli/definitions/billing.json @@ -2,21 +2,58 @@ "openapi": "3.0.3", "info": { "title": "Billing API", - "description": "API to manage billing data for epilot contracts and orders", + "description": "API to manage billing data for epilot contracts and orders.\n\nThis API provides endpoints for managing financial transactions (Buchungssätze) related to\ncustomer contracts (Verträge) including installments (Abschlagszahlungen), payments (Zahlungseingänge),\nreimbursements (Rückerstattungen), and other billing events.\n\n## Key Concepts\n\n- **Billing Event (Buchungssatz)**: A single financial transaction entry in the billing ledger\n- **Contract (Vertrag)**: A customer agreement linked to billing events\n- **Balance (Kontostand)**: The current financial standing of a customer across all contracts\n", "version": "1.0.0" }, "tags": [ { "name": "Billing Events", - "description": "Manage installments, payments and reimbursements" + "description": "Manage billing events (Buchungssätze) such as installments (Abschlagszahlungen),\npayments (Zahlungseingänge), reimbursements (Rückerstattungen), dunning fees (Mahngebühren),\nand other financial transactions.\n\nBilling events follow double-entry accounting principles where each entry is either\na debit (Soll) or credit (Haben) transaction.\n" }, { "name": "Contracts", - "description": "Manage contracts within 360" + "description": "Manage contract entities (Verträge) within epilot 360.\n\nContracts represent customer agreements and serve as the parent entity for billing events.\nEach contract has associated billing settings such as billing period (Abrechnungszeitraum),\ninstallment amounts (Abschlagsbeträge), and balance tracking.\n" }, { "name": "Balance", - "description": "Get customer balance" + "description": "Retrieve customer balance information (Kontostand).\n\nThe balance represents the total financial standing of a customer across all their\ncontracts and orders, calculated from the sum of all billing events.\n" + }, + { + "name": "Pricing Information", + "description": "Read contract and billing account pricing information" + }, + { + "name": "Configuration History", + "description": "Read billing configuration change history" + }, + { + "name": "billing_event_schema", + "x-displayName": "Billing Event", + "description": "\n" + }, + { + "name": "contract_schema", + "x-displayName": "Contract", + "description": "\n" + } + ], + "x-tagGroups": [ + { + "name": "APIs", + "tags": [ + "Billing Events", + "Contracts", + "Balance", + "Pricing Information", + "Configuration History" + ] + }, + { + "name": "Schemas", + "tags": [ + "billing_event_schema", + "contract_schema" + ] } ], "security": [ @@ -29,29 +66,32 @@ "get": { "operationId": "getBillingEvents", "summary": "getBillingEvents", - "description": "Get and filter billing events such as installments and reimbursements.", + "description": "Retrieve and filter billing events (Buchungssätze) such as installments (Abschlagszahlungen),\npayments (Zahlungseingänge), and reimbursements (Rückerstattungen).\n\nResults are paginated and can be filtered by entity, contact, event type, or date range.\n", "tags": [ "Billing Events" ], "parameters": [ { - "in": "query", "name": "from", + "in": "query", "required": false, + "description": "Pagination offset - number of results to skip", "schema": { "type": "number", - "description": "Initial offset to set for the search results", + "minimum": 0, "example": 0, "default": 0 } }, { - "in": "query", "name": "size", + "in": "query", "required": false, + "description": "Maximum number of results to return per page", "schema": { "type": "number", - "description": "Size of the search results", + "minimum": 1, + "maximum": 1000, "example": 100, "default": 100 } @@ -59,87 +99,158 @@ { "name": "entity_id", "in": "query", - "description": "Entity ID to filter billing events by", "required": false, + "description": "Filter billing events by one or more entity IDs (e.g., contract or order IDs)", "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "example": "5da0a718-c822-403d-9f5d-20d4584e0528" } + }, + "example": [ + "5da0a718-c822-403d-9f5d-20d4584e0528" + ] + }, + { + "name": "contact_id", + "in": "query", + "required": false, + "description": "Filter billing events by customer contact ID (Kundennummer)", + "schema": { + "type": "string", + "example": "1e3f0d58-69d2-4dbb-9a43-3ee63d862e8e" } }, { "name": "event_type", "in": "query", + "description": "Filter by billing event type (Buchungsart):\n- `installment`: Abschlagszahlung (scheduled payment due)\n- `reimbursement`: Rückerstattung (refund to customer)\n", "schema": { "type": "string", "enum": [ "installment", "reimbursement" ], - "description": "Type of billing event to filter by" + "example": "installment" } }, { "name": "date_after", "in": "query", + "description": "Filter billing events with booking date (Buchungsdatum) after this timestamp", "schema": { "type": "string", "format": "date-time", - "description": "List billing events after this date" + "example": "2025-01-01T00:00:00Z" } }, { "name": "date_before", "in": "query", + "description": "Filter billing events with booking date (Buchungsdatum) before this timestamp", "schema": { "type": "string", "format": "date-time", - "description": "List billing events before this date" + "example": "2025-12-31T23:59:59Z" } } ], "responses": { "200": { - "description": "List of billing events", + "description": "Paginated list of billing events matching the filter criteria", "content": { "application/json": { "schema": { "type": "object", "properties": { "hits": { - "type": "integer" + "type": "integer", + "description": "Total number of billing events matching the query", + "example": 42 }, "results": { "type": "array", + "description": "List of billing events for the current page", "items": { "$ref": "#/components/schemas/BillingEvent" } } } + }, + "example": { + "hits": 42, + "results": [ + { + "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", + "_schema": "billing_event", + "type": "installment", + "direction": "debit", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR", + "booking_date": "2025-07-10", + "due_date": "2025-07-15", + "status": "open", + "contract": { + "$relation": [ + { + "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" + } + ] + } + } + ] } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } }, "post": { "operationId": "createBillingEvent", "summary": "createBillingEvent", - "description": "Create a new billing event.", + "description": "Create a new billing event (Buchungssatz) such as an installment (Abschlagszahlung),\npayment (Zahlungseingang), or reimbursement (Rückerstattung).\n\nThe billing event will be linked to a contract and will affect the customer's balance.\n", "tags": [ "Billing Events" ], "requestBody": { "required": true, + "description": "Billing event data to create", "content": { "application/json": { "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/BillingEvent" - } - ] + "$ref": "#/components/schemas/BillingEvent" + }, + "example": { + "type": "installment", + "direction": "debit", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR", + "booking_date": "2025-07-10", + "due_date": "2025-07-15", + "status": "open", + "note": "Monthly installment for July", + "contract": { + "$relation": [ + { + "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" + } + ] + } } } } @@ -150,14 +261,25 @@ "content": { "application/json": { "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/BillingEvent" - } - ] + "$ref": "#/components/schemas/BillingEvent" } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } } @@ -166,7 +288,7 @@ "get": { "operationId": "getBillingEvent", "summary": "getBillingEvent", - "description": "Get a single billing event by ID.", + "description": "Retrieve a single billing event (Buchungssatz) by its unique ID.\n", "tags": [ "Billing Events" ], @@ -175,15 +297,16 @@ "name": "id", "in": "path", "required": true, - "description": "ID of the billing event to get", + "description": "Unique identifier of the billing event (Buchungssatz-ID)", "schema": { - "type": "string" + "type": "string", + "example": "5da0a718-c822-403d-9f5d-20d4584e0528" } } ], "responses": { "200": { - "description": "Billing Event", + "description": "Billing event details", "content": { "application/json": { "schema": { @@ -191,13 +314,25 @@ } } } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } }, "patch": { "operationId": "updateBillingEvent", "summary": "updateBillingEvent", - "description": "Update an existing billing event.", + "description": "Update an existing billing event (Buchungssatz).\n\nOnly the fields provided in the request body will be updated.\nCommon use cases include updating status from \"open\" to \"closed\" after payment,\nor correcting billing amounts.\n", "tags": [ "Billing Events" ], @@ -206,25 +341,32 @@ "name": "id", "in": "path", "required": true, - "description": "ID of the billing event to update", + "description": "Unique identifier of the billing event to update", "schema": { - "type": "string" + "type": "string", + "example": "5da0a718-c822-403d-9f5d-20d4584e0528" } } ], "requestBody": { "required": true, + "description": "Fields to update on the billing event", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BillingEvent" + "$ref": "#/components/schemas/BillingEventUpdate" + }, + "example": { + "status": "closed", + "paid_date": "2025-07-12T14:30:00Z", + "note": "Payment received via bank transfer" } } } }, "responses": { "200": { - "description": "Billing Event updated successfully", + "description": "Billing event updated successfully", "content": { "application/json": { "schema": { @@ -232,13 +374,31 @@ } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } }, "delete": { "operationId": "deleteBillingEvent", "summary": "deleteBillingEvent", - "description": "Delete an existing billing event.", + "description": "Delete an existing billing event (Buchungssatz).\n\n**Warning**: Deleting billing events affects the customer's balance calculation.\nConsider using a correction event (Korrekturbuchung) instead for audit purposes.\n", "tags": [ "Billing Events" ], @@ -247,15 +407,28 @@ "name": "id", "in": "path", "required": true, - "description": "ID of the billing event to delete", + "description": "Unique identifier of the billing event to delete", "schema": { - "type": "string" + "type": "string", + "example": "5da0a718-c822-403d-9f5d-20d4584e0528" } } ], "responses": { "204": { - "description": "Billing Event deleted successfully" + "description": "Billing event deleted successfully" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } } @@ -264,7 +437,7 @@ "get": { "operationId": "getBillingEventByExternalId", "summary": "getBillingEventByExternalId", - "description": "Get a single billing event by External ID.", + "description": "Retrieve a billing event (Buchungssatz) by its external system identifier.\n\nThis endpoint is useful for integrations with external systems such as SAP FI/CA,\nERP systems, or payment processors that maintain their own reference IDs.\n", "tags": [ "Billing Events" ], @@ -273,15 +446,16 @@ "name": "external_id", "in": "path", "required": true, - "description": "ID of the billing event to get", + "description": "External system identifier for the billing event.\nFor example, a SAP document number or payment processor reference ID.\n", "schema": { - "type": "string" + "type": "string", + "example": "SAP-54321" } } ], "responses": { "200": { - "description": "Billing Event", + "description": "Billing event details", "content": { "application/json": { "schema": { @@ -289,6 +463,18 @@ } } } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } } @@ -297,16 +483,28 @@ "post": { "operationId": "createContractEntity", "summary": "createContractEntity", - "description": "Create a new contract entity.", + "description": "Create a new contract entity (Vertrag) for billing purposes.\n\nContracts serve as the parent entity for billing events and contain billing\nsettings such as installment amounts (Abschlagsbeträge), billing periods\n(Abrechnungszeiträume), and customer addresses.\n", "tags": [ "Contracts" ], "requestBody": { "required": true, + "description": "Contract data to create", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Contract" + }, + "example": { + "contract_name": "Stromvertrag Haushalt", + "contract_number": "STR-2025-001234", + "status": "active", + "branch": "power", + "billing_period": "monthly", + "installment_amount": 8500, + "billing_due_day": 15, + "start_date": "2025-01-01", + "billing_address": "Musterstraße 123, 50667 Köln" } } } @@ -321,6 +519,21 @@ } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } } @@ -329,7 +542,7 @@ "patch": { "operationId": "updateContractEntity", "summary": "updateContractEntity", - "description": "Update an existing contract entity.", + "description": "Update an existing contract entity (Vertrag).\n\nOnly the fields provided in the request body will be updated.\nCommon use cases include updating billing settings, changing status,\nor recording termination details.\n", "tags": [ "Contracts" ], @@ -338,18 +551,25 @@ "name": "id", "in": "path", "required": true, - "description": "ID of the contract entity to update", + "description": "Unique identifier of the contract (Vertragsnummer)", "schema": { - "type": "string" + "type": "string", + "example": "f589786b-3024-43cd-9cb3-5a3c953f2896" } } ], "requestBody": { "required": true, + "description": "Fields to update on the contract", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Contract" + }, + "example": { + "status": "terminated", + "termination_date": "2025-06-30", + "termination_reason": "Kundenkündigung" } } } @@ -364,13 +584,31 @@ } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } }, "delete": { "operationId": "deleteContractEntity", "summary": "deleteContractEntity", - "description": "Delete an existing contract entity.", + "description": "Delete an existing contract entity (Vertrag).\n\n**Warning**: Deleting a contract will affect all associated billing events.\nConsider setting the contract status to \"terminated\" instead for audit purposes.\n", "tags": [ "Contracts" ], @@ -379,19 +617,193 @@ "name": "id", "in": "path", "required": true, - "description": "ID of the contract entity to delete", + "description": "Unique identifier of the contract to delete", "schema": { - "type": "string" + "type": "string", + "example": "f589786b-3024-43cd-9cb3-5a3c953f2896" } } ], "responses": { "204": { - "description": "Contract deleted successfully", + "description": "Contract deleted successfully" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/billing/contracts/{id}/pricing_information": { + "get": { + "operationId": "getContractPricingInformation", + "summary": "getContractPricingInformation", + "description": "Get current pricing information and recent configuration history for a Contract.", + "tags": [ + "Pricing Information" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "ID of the Contract entity", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/HistoryChangeTypes" + }, + { + "$ref": "#/components/parameters/IncludeHistory" + } + ], + "responses": { + "200": { + "description": "Contract pricing information", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ContractItem" + "$ref": "#/components/schemas/ContractPricingInformation" + } + } + } + } + } + } + }, + "/v1/billing/billing_accounts/{id}/pricing_information": { + "get": { + "operationId": "getBillingAccountPricingInformation", + "summary": "getBillingAccountPricingInformation", + "description": "Get current pricing information for the active Contracts linked to a Billing Account.", + "tags": [ + "Pricing Information" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "ID of the Billing Account entity", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/HistoryChangeTypes" + }, + { + "$ref": "#/components/parameters/IncludeHistory" + } + ], + "responses": { + "200": { + "description": "Billing Account pricing information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BillingAccountPricingInformation" + } + } + } + } + } + } + }, + "/v1/billing/contracts/{id}/configuration_history": { + "get": { + "operationId": "getContractConfigurationHistory", + "summary": "getContractConfigurationHistory", + "description": "Get billing configuration history for a Contract.", + "tags": [ + "Configuration History" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "ID of the Contract entity", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ConfigurationHistoryChangeType" + }, + { + "$ref": "#/components/parameters/HistoryChangeTypes" + }, + { + "$ref": "#/components/parameters/From" + }, + { + "$ref": "#/components/parameters/Size" + } + ], + "responses": { + "200": { + "description": "Contract configuration history", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigurationHistoryResponse" + } + } + } + } + } + } + }, + "/v1/billing/billing_accounts/{id}/configuration_history": { + "get": { + "operationId": "getBillingAccountConfigurationHistory", + "summary": "getBillingAccountConfigurationHistory", + "description": "Get merged billing configuration history for active Contracts linked to a Billing Account.", + "tags": [ + "Configuration History" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "ID of the Billing Account entity", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ConfigurationHistoryChangeType" + }, + { + "$ref": "#/components/parameters/HistoryChangeTypes" + }, + { + "$ref": "#/components/parameters/From" + }, + { + "$ref": "#/components/parameters/Size" + } + ], + "responses": { + "200": { + "description": "Billing Account configuration history", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigurationHistoryResponse" } } } @@ -403,7 +815,7 @@ "get": { "operationId": "getCustomerBalance", "summary": "getCustomerBalance", - "description": "Get total balance across all contracts and orders of a customer entity.", + "description": "Retrieve the total balance (Kontostand) across all contracts and orders for a customer.\n\nThe balance is calculated from the sum of all billing events (Buchungssätze) associated\nwith the customer's contracts. A positive balance indicates the customer owes money;\na negative balance indicates a credit (Guthaben) in the customer's favor.\n", "tags": [ "Balance" ], @@ -411,7 +823,7 @@ { "name": "id", "in": "path", - "description": "Customer entity ID (contact or account)", + "description": "Customer entity ID. This can be either a contact ID (Kontakt-ID) or\nan account ID (Kundenkonto-ID).\n", "required": true, "schema": { "type": "string", @@ -421,20 +833,97 @@ ], "responses": { "200": { - "description": "OK", + "description": "Customer balance information", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Balance" + }, + "example": { + "balance": 8990, + "balance_decimal": "89.90", + "balance_currency": "EUR" } } } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } } } }, "components": { + "parameters": { + "From": { + "name": "from", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "description": "Initial offset for paginated results." + }, + "Size": { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "description": "Maximum number of results to return." + }, + "ConfigurationHistoryChangeType": { + "name": "change_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "installment_amount_changed", + "contract_pricing_changed" + ], + "default": "installment_amount_changed" + }, + "description": "Billing configuration change type to return." + }, + "HistoryChangeTypes": { + "name": "history_change_types", + "in": "query", + "required": false, + "schema": { + "type": "string", + "pattern": "^(installment_amount_changed|contract_pricing_changed)(,(installment_amount_changed|contract_pricing_changed))*$", + "example": "installment_amount_changed,contract_pricing_changed" + }, + "description": "Comma-separated billing configuration change types to return. When omitted, installment history is returned by default." + }, + "IncludeHistory": { + "name": "include_history", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true + }, + "description": "Include recent configuration history in the pricing information response. Set to false when using the dedicated configuration history endpoint." + } + }, "securitySchemes": { "EpilotAuth": { "type": "http", @@ -443,108 +932,458 @@ "bearerFormat": "JWT" } }, - "schemas": { - "BaseEntity": { - "type": "object", - "additionalProperties": true, - "properties": { - "_id": { - "$ref": "#/components/schemas/EntityId" - }, - "_title": { - "type": "string", - "description": "Title of entity" - }, - "_org": { - "type": "string", - "description": "Organization Id the entity belongs to" - }, - "_schema": { - "$ref": "#/components/schemas/EntitySlug" - }, - "_tags": { - "type": "array", + "responses": { + "BadRequest": { + "description": "Bad Request - The request was malformed or contains invalid parameters.\nCheck the request body and query parameters for syntax errors.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": "Bad Request", + "message": "Invalid date format for 'due_date'. Expected ISO 8601 date format (YYYY-MM-DD)." + } + } + } + }, + "Unauthorized": { + "description": "Unauthorized - Authentication credentials are missing or invalid.\nEnsure a valid Bearer token is included in the Authorization header.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": "Unauthorized", + "message": "Missing or invalid authentication token." + } + } + } + }, + "Forbidden": { + "description": "Forbidden - The authenticated user does not have permission to perform this action.\nContact your organization administrator to request access.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource." + } + } + } + }, + "NotFound": { + "description": "Not Found - The requested resource does not exist or has been deleted.\nVerify the ID is correct and the resource exists in your organization.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": "Not Found", + "message": "Billing event with ID '5da0a718-c822-403d-9f5d-20d4584e0528' not found." + } + } + } + }, + "UnprocessableEntity": { + "description": "Unprocessable Entity - The request was well-formed but contains semantic errors.\nThis typically indicates validation failures such as missing required fields\nor invalid field values.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": "Unprocessable Entity", + "message": "Validation failed: 'billing_amount' must be a positive integer." + } + } + } + }, + "InternalServerError": { + "description": "Internal Server Error - An unexpected error occurred on the server.\nPlease try again later or contact support if the issue persists.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": "Internal Server Error", + "message": "An unexpected error occurred. Please try again later." + } + } + } + } + }, + "schemas": { + "Error": { + "type": "object", + "description": "Standard error response format", + "properties": { + "error": { + "type": "string", + "description": "Error type or HTTP status text", + "example": "Bad Request" + }, + "message": { + "type": "string", + "description": "Human-readable error description", + "example": "Invalid request parameters." + } + }, + "required": [ + "error", + "message" + ] + }, + "BaseEntity": { + "type": "object", + "description": "Base schema for all epilot entities with common system fields", + "additionalProperties": true, + "properties": { + "_id": { + "$ref": "#/components/schemas/EntityId" + }, + "_title": { + "type": "string", + "description": "Display title of the entity (Anzeigetitel)", + "example": "Abschlagszahlung Juli 2025" + }, + "_org": { + "type": "string", + "description": "Organization ID the entity belongs to (Organisations-ID)", + "example": "123456" + }, + "_schema": { + "$ref": "#/components/schemas/EntitySlug" + }, + "_tags": { + "type": "array", + "description": "Tags for categorization and filtering (Schlagwörter)", "items": { "type": "string" - } + }, + "example": [ + "billing", + "energy" + ] }, "_created_at": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "Timestamp when the entity was created (Erstellungszeitpunkt)", + "example": "2025-06-15T10:30:00Z" }, "_updated_at": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "Timestamp when the entity was last updated (Aktualisierungszeitpunkt)", + "example": "2025-06-15T14:45:00Z" } } }, "EntityId": { "type": "string", - "description": "Entity ID", + "description": "Unique entity identifier (UUID format)", "example": "5da0a718-c822-403d-9f5d-20d4584e0528" }, "EntitySlug": { - "description": "URL-friendly identifier for the entity schema", "type": "string", - "enum": [ - "contact", - "contract", - "file", - "order", - "opportunity", - "product", - "price", - "meter", - "meter_counter", - "billing_event" - ], - "example": "contact" + "description": "URL-friendly identifier for the entity schema (Schema-Slug)", + "example": "billing_event" }, "EntityRelationItem": { "type": "object", + "description": "Reference to a related entity", "properties": { "entity_id": { "type": "string", - "description": "Entity ID for the related contract.", + "description": "Entity ID for the related entity (e.g., contract or order)", "example": "f589786b-3024-43cd-9cb3-5a3c953f2896" } } }, "BaseBillingEvent": { + "description": "Represents a single financial transaction entry (Buchungssatz) in the billing ledger.\nEach entry is either a debit or a credit, following double-entry accounting principles.\nCommon types include Abschlagszahlung (installment), Zahlungseingang (payment), Rückerstattung (reimbursement), etc.\n", "allOf": [ { "$ref": "#/components/schemas/BaseEntity" + }, + { + "type": "object", + "required": [ + "type", + "billing_amount", + "billing_amount_decimal", + "billing_currency", + "booking_date", + "contract" + ], + "properties": { + "type": { + "type": "string", + "description": "The classification of the billing transaction.\nThis field is used to group financial events for reporting or reconciliation.\nCommon examples:\n - installment (Abschlagszahlung)\n - payment (Zahlungseingang)\n - reimbursement (Rückerstattung)\n - dunning_fee (Mahngebühr)\n - chargeback (Lastschrift-Rückgabe)\n - final_bill (Endabrechnung)\n - bonus (Gutschrift)\n - correction (Korrekturbuchung)\n", + "example": "installment" + }, + "direction": { + "type": "string", + "enum": [ + "debit", + "credit" + ], + "description": "The accounting direction of the transaction:\n- debit (Soll): increases the customer’s liability (e.g. invoice issued)\n- credit (Haben): reduces the liability (e.g. payment received)\n\nAutomatically inferred based on event type if not specified.\n", + "example": "debit" + }, + "billing_amount": { + "type": "integer", + "description": "Betrag in Cent (net or gross depending on context)", + "example": 10000 + }, + "billing_amount_decimal": { + "type": "string", + "description": "Decimal representation of billing_amount for display or reporting", + "example": "100.00" + }, + "billing_currency": { + "$ref": "#/components/schemas/Currency" + }, + "external_id": { + "type": "string", + "description": "External system identifier (e.g. SAP FI/CA document number, Zahlungsavis ID)", + "example": "SAP-54321" + }, + "contract": { + "type": "object", + "description": "Link to the associated Vertragskonto or contract object", + "properties": { + "$relation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityRelationItem" + } + } + } + }, + "booking_date": { + "type": "string", + "format": "date", + "description": "General ledger booking date (Buchungsdatum) for the billing event.\nUsed to determine when the entry was accounted for in the system.\nApplies to all event types (debits and credits).\nCan be used together with due_date or paid_date\n", + "example": "2025-06-15" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date of the invoice or charge (Fälligkeitsdatum)", + "example": "2025-06-30" + }, + "paid_date": { + "type": "string", + "format": "date-time", + "description": "Booking date (Zahlungseingang, Wertstellung)", + "example": "2025-06-15T10:00:00Z" + }, + "status": { + "type": "string", + "description": "Status of the billing event. Defaults to closed.", + "enum": [ + "closed", + "open" + ] + }, + "related_event": { + "type": "string", + "description": "Optional reference to a previous event e.g. a chargeback or a correction to a previous installment", + "example": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", + "properties": { + "$relation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityRelationItem" + } + } + } + }, + "external_link": { + "type": "object", + "description": "Optional link to an external resource e.g. an invoice in a customer portal", + "properties": { + "href": { + "type": "string", + "format": "uri", + "description": "URL of the external resource", + "example": "https://billing.example.com/invoices/12345" + }, + "title": { + "type": "string", + "description": "Title of the external resource", + "example": "Invoice 12345" + } + }, + "required": [ + "url" + ] + }, + "attachments": { + "type": "object", + "description": "Optional reference to e.g. an invoice file associated with the billing event", + "properties": { + "$relation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityRelationItem" + } + } + } + }, + "note": { + "type": "string", + "description": "Öffentliche Notiz sichtbar für den Kunden, z.B. auf der Rechnung oder im Kundenportal", + "example": "Teilzahlung für Abschlag Juni" + }, + "internal_note": { + "type": "string", + "description": "Interne Notiz, nur sichtbar für Sachbearbeiter (nicht für Kunden sichtbar)", + "example": "Rückmeldung von SAP: Betrag aus Zahlungsavis 2025-06-14 übernommen" + } + } } - ], - "description": "A base billing event to be inherited by all billing events.", + ] + }, + "BillingEvent": { + "description": "Collection of supported billing event types (Buchungsarten).\nEach type represents a different kind of financial transaction\nthat affects the customer's balance.\n", + "oneOf": [ + { + "$ref": "#/components/schemas/InstallmentEvent" + }, + { + "$ref": "#/components/schemas/PaymentEvent" + }, + { + "$ref": "#/components/schemas/ReimbursementEvent" + }, + { + "$ref": "#/components/schemas/DunningFeeEvent" + }, + { + "$ref": "#/components/schemas/InvoiceEvent" + }, + { + "$ref": "#/components/schemas/FinalBillEvent" + }, + { + "$ref": "#/components/schemas/BonusEvent" + }, + { + "$ref": "#/components/schemas/CorrectionEvent" + }, + { + "$ref": "#/components/schemas/CustomEvent" + } + ] + }, + "BillingEventUpdate": { + "description": "Fields to update on an existing billing event.", "type": "object", - "required": [ - "type", - "contract", - "billing_amount", - "billing_amount_decimal", - "billing_currency" - ], - "additionalProperties": true, + "minProperties": 1, "properties": { + "type": { + "type": "string", + "description": "Updated billing event type.", + "example": "installment" + }, + "direction": { + "type": "string", + "enum": [ + "debit", + "credit" + ], + "description": "Updated accounting direction.", + "example": "debit" + }, "billing_amount": { "type": "integer", - "description": "Amount to be paid in cents i.e. precision 2", - "example": 10050 + "description": "Updated amount in cents.", + "example": 10000 }, "billing_amount_decimal": { "type": "string", - "description": "Amount to be paid in cents in decimal string representation", - "example": "100.50" + "description": "Updated decimal representation of billing_amount.", + "example": "100.00" }, "billing_currency": { "$ref": "#/components/schemas/Currency" }, "external_id": { "type": "string", - "description": "Unique identifier for event, used to reference the event to a 3rd party resource such as a SAP Installment.", - "example": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e" + "description": "Updated external system identifier.", + "example": "SAP-54321" }, "contract": { "type": "object", + "description": "Updated link to the associated contract object.", + "properties": { + "$relation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityRelationItem" + } + } + } + }, + "booking_date": { + "type": "string", + "format": "date", + "description": "Updated general ledger booking date.", + "example": "2025-06-15" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Updated due date.", + "example": "2025-06-30" + }, + "paid_date": { + "type": "string", + "format": "date-time", + "description": "Updated payment date.", + "example": "2025-06-15T10:00:00Z" + }, + "status": { + "type": "string", + "enum": [ + "closed", + "open" + ], + "description": "Updated billing event status." + }, + "related_event": { + "type": "string", + "description": "Updated reference to a previous event.", + "example": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e" + }, + "external_link": { + "type": "object", + "description": "Updated link to an external resource.", + "properties": { + "href": { + "type": "string", + "format": "uri", + "description": "URL of the external resource", + "example": "https://billing.example.com/invoices/12345" + }, + "title": { + "type": "string", + "description": "Title of the external resource", + "example": "Invoice 12345" + } + } + }, + "attachments": { + "type": "object", + "description": "Updated invoice file relations associated with the billing event.", "properties": { "$relation": { "type": "array", @@ -553,11 +1392,21 @@ } } } + }, + "note": { + "type": "string", + "description": "Updated public note visible to the customer.", + "example": "Teilzahlung für Abschlag Juni" + }, + "internal_note": { + "type": "string", + "description": "Updated internal note visible to agents only.", + "example": "Rückmeldung von SAP: Betrag aus Zahlungsavis 2025-06-14 übernommen" } } }, "InstallmentEvent": { - "description": "An entity that describes an installment billing event.", + "description": "Installment billing event (Abschlagszahlung).\nRepresents a scheduled partial payment that the customer owes,\ntypically billed monthly for utilities like electricity or gas.\n", "allOf": [ { "$ref": "#/components/schemas/BaseBillingEvent" @@ -570,28 +1419,28 @@ "enum": [ "installment" ], - "description": "Type of the billing event.", - "example": "installment" - }, - "due_date": { - "type": "string", - "format": "date-time", - "description": "Date on which the installment is due." - }, - "paid_date": { - "type": "string", - "format": "date-time", - "description": "Date on which the installment is paid by the customer." + "description": "Event type identifier" } }, + "example": { + "type": "installment", + "direction": "debit", + "note": "July power & gas installment payment", + "status": "open", + "booking_date": "2025-07-10", + "due_date": "2025-07-10", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" + }, "required": [ "due_date" ] } ] }, - "ReimbursementEvent": { - "description": "An entity that describes a reimbursement billing event.", + "PaymentEvent": { + "description": "Payment received event (Zahlungseingang).\nRepresents money received from the customer, reducing their balance.\nThis is a credit transaction.\n", "allOf": [ { "$ref": "#/components/schemas/BaseBillingEvent" @@ -602,74 +1451,287 @@ "type": { "type": "string", "enum": [ - "reimbursement" + "payment" ], - "description": "Type of the billing event.", - "example": "reimbursement" - }, - "paid_date": { - "type": "string", - "format": "date-time", - "description": "Date on which the customer is reimbursed." + "description": "Event type identifier" } + }, + "example": { + "type": "payment", + "direction": "credit", + "note": "Payment reference 001234567", + "status": "closed", + "booking_date": "2025-06-15", + "due_date": "2025-06-15", + "paid_date": "2025-06-09T10:00:00Z", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" } } ] }, - "BillingEvent": { - "description": "An entity that describes a billing event such as a future installment or a reimbursement back to the customer.", - "oneOf": [ + "ReimbursementEvent": { + "description": "Reimbursement event (Rückerstattung).\nRepresents a refund to the customer, typically after overpayment\nor billing correction. This is a credit transaction.\n", + "allOf": [ { - "$ref": "#/components/schemas/InstallmentEvent" + "$ref": "#/components/schemas/BaseBillingEvent" }, { - "$ref": "#/components/schemas/ReimbursementEvent" + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "reimbursement" + ], + "description": "Event type identifier" + } + }, + "example": { + "type": "reimbursement", + "direction": "credit", + "billing_amount": 10000, + "billing_amount_decimal": "100.00", + "billing_currency": "EUR", + "note": "Refund due to meter correction", + "booking_date": "2025-06-16", + "paid_date": "2025-06-18T14:00:00Z" + } } ] }, - "Contract": { + "DunningFeeEvent": { + "description": "Dunning fee event (Mahngebühr).\nRepresents a late payment fee charged to the customer\nafter a payment reminder has been sent. This is a debit transaction.\n", "allOf": [ { - "$ref": "#/components/schemas/BaseEntity" + "$ref": "#/components/schemas/BaseBillingEvent" }, { "type": "object", "properties": { - "contract_name": { - "type": "string", - "description": "The name of the contract.", - "example": "Grid Contract" - }, - "contract_number": { - "type": "string", - "description": "The unique identifier of the contract.", - "example": "12345" - }, - "status": { + "type": { "type": "string", "enum": [ - "draft", - "in_approval_process", - "approved", - "active", - "deactivated", - "revoked", - "terminated", - "expired" + "dunning_fee" ], - "default": "draft", - "description": "The status of the contract.", - "example": "approved" - }, - "description": { - "type": "string", - "description": "A brief description of the contract.", - "example": "This contract is for the supply of widgets." + "description": "Event type identifier" + } + }, + "example": { + "type": "dunning_fee", + "direction": "debit", + "note": "Late fee for April invoice", + "status": "open", + "booking_date": "2025-06-10", + "due_date": "2025-06-10", + "billing_amount": 1500, + "billing_amount_decimal": "15.00", + "billing_currency": "EUR" + } + } + ] + }, + "InvoiceEvent": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseBillingEvent" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "invoice" + ] + } + }, + "example": { + "type": "invoice", + "direction": "debit", + "billing_amount": 8500, + "billing_amount_decimal": "85.00", + "billing_currency": "EUR", + "note": "Einmalige Rechnung für Zusatzleistung", + "booking_date": "2025-06-25", + "due_date": "2025-07-01" + }, + "required": [ + "due_date" + ] + } + ] + }, + "FinalBillEvent": { + "description": "Final bill event (Endabrechnung/Schlussrechnung).\nRepresents the final settlement when a contract ends,\naccounting for actual consumption vs. paid installments.\nCan be either debit (customer owes more) or credit (customer overpaid).\n", + "allOf": [ + { + "$ref": "#/components/schemas/BaseBillingEvent" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "final_bill" + ], + "description": "Event type identifier" + } + }, + "example": { + "type": "final_bill", + "direction": "debit", + "billing_amount": 12000, + "billing_amount_decimal": "120.00", + "billing_currency": "EUR", + "note": "Final invoice after contract termination", + "booking_date": "2025-06-30" + } + } + ] + }, + "BonusEvent": { + "description": "Bonus/credit event (Gutschrift/Bonus).\nRepresents a promotional credit or bonus applied to the customer's account,\nsuch as welcome bonuses or loyalty rewards. This is a credit transaction.\n", + "allOf": [ + { + "$ref": "#/components/schemas/BaseBillingEvent" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "bonus" + ], + "description": "Event type identifier" + } + }, + "example": { + "type": "bonus", + "direction": "credit", + "billing_amount": 1500, + "billing_amount_decimal": "15.00", + "billing_currency": "EUR", + "note": "Welcome bonus", + "booking_date": "2025-06-15" + } + } + ] + }, + "CorrectionEvent": { + "description": "Correction event (Korrekturbuchung).\nRepresents an adjustment to a previous billing entry,\nsuch as correcting an overcharge or undercharge.\nCan be either debit or credit depending on the correction.\n", + "allOf": [ + { + "$ref": "#/components/schemas/BaseBillingEvent" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "correction" + ], + "description": "Event type identifier" + } + }, + "example": { + "type": "correction", + "direction": "credit", + "billing_amount": 200, + "billing_amount_decimal": "2.00", + "billing_currency": "EUR", + "note": "Corrected previous overcharge", + "booking_date": "2025-06-22" + } + } + ] + }, + "CustomEvent": { + "description": "Custom billing event (Benutzerdefinierte Buchung).\nAllows for organization-specific billing event types not covered\nby the standard types. Use a descriptive type name.\n", + "allOf": [ + { + "$ref": "#/components/schemas/BaseBillingEvent" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "not": { + "enum": [ + "installment", + "payment", + "reimbursement", + "dunning_fee", + "invoice", + "final_bill", + "bonus", + "correction" + ] + }, + "description": "Custom billing event type (Freitext).\nUse a descriptive identifier for your custom event type.\nExamples: grid_fee_adjustment, meter_rental, special_charge\n" + } + }, + "example": { + "type": "grid_fee_adjustment", + "direction": "debit", + "billing_amount": 3200, + "billing_amount_decimal": "32.00", + "billing_currency": "EUR", + "note": "Netznachberechnung für Mai", + "booking_date": "2025-06-11" + } + } + ] + }, + "Contract": { + "description": "Represents a customer contract (Vertrag) for billing purposes.\nContracts are the parent entities for billing events and contain\nbilling configuration such as installment amounts and billing cycles.\n", + "allOf": [ + { + "$ref": "#/components/schemas/BaseEntity" + }, + { + "type": "object", + "properties": { + "contract_name": { + "type": "string", + "description": "Display name of the contract (Vertragsname)", + "example": "Stromvertrag Haushalt" + }, + "contract_number": { + "type": "string", + "description": "Unique contract identifier/number (Vertragsnummer)", + "example": "STR-2025-001234" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "in_approval_process", + "approved", + "active", + "deactivated", + "revoked", + "terminated", + "expired" + ], + "default": "draft", + "description": "Current status of the contract (Vertragsstatus):\n- draft (Entwurf): Contract is being prepared\n- in_approval_process (In Prüfung): Awaiting approval\n- approved (Genehmigt): Approved but not yet active\n- active (Aktiv): Contract is currently active\n- deactivated (Deaktiviert): Temporarily suspended\n- revoked (Widerrufen): Cancelled by customer within cooling-off period\n- terminated (Gekündigt): Contract has been terminated\n- expired (Abgelaufen): Contract term has ended\n", + "example": "active" + }, + "description": { + "type": "string", + "description": "Brief description of the contract terms (Vertragsbeschreibung)", + "example": "Haushaltsstrom-Tarif mit 24 Monaten Preisgarantie" }, "account_number": { "type": "string", - "description": "The account number associated with the contract.", - "example": "67890" + "description": "Customer account number (Kundennummer/Vertragskonto)", + "example": "KD-67890" }, "branch": { "type": "string", @@ -680,33 +1742,34 @@ "waste_water", "district_heating" ], - "description": "The branch associated with the contract.", + "description": "Utility branch/commodity type (Sparte):\n- power (Strom)\n- gas (Gas)\n- water (Wasser)\n- waste_water (Abwasser)\n- district_heating (Fernwärme)\n", "example": "power" }, "billing_address": { "type": "string", - "description": "The billing address associated with the contract.", - "example": "123 Main St, Anytown" + "description": "Billing/invoice address (Rechnungsadresse)", + "example": "Musterstraße 123, 50667 Köln" }, "delivery_address": { "type": "string", - "description": "The delivery address associated with the contract.", - "example": "456 Elm St, Anytown" + "description": "Delivery/supply point address (Lieferadresse/Verbrauchsstelle)", + "example": "Musterstraße 123, 50667 Köln" }, "additional_addresses": { "type": "string", - "description": "Any additional addresses associated with the contract.", - "example": "789 Oak St, Anytown" + "description": "Additional addresses associated with the contract (Weitere Adressen)", + "example": "Postfach 456, 50668 Köln" }, "termination_date": { "type": "string", - "description": "The date on which the contract was terminated.", - "example": "2022-01-01" + "format": "date", + "description": "Date when the contract was/will be terminated (Kündigungsdatum)", + "example": "2025-12-31" }, "termination_reason": { "type": "string", - "description": "The reason for the termination of the contract.", - "example": "Non-payment" + "description": "Reason for contract termination (Kündigungsgrund)", + "example": "Kundenkündigung" }, "billing_period": { "type": "string", @@ -718,20 +1781,20 @@ "yearly" ], "default": "weekly", - "description": "The billing period associated with the contract.", + "description": "Billing cycle frequency (Abrechnungszeitraum):\n- weekly (Wöchentlich)\n- monthly (Monatlich)\n- every_quarter (Vierteljährlich)\n- every_6_months (Halbjährlich)\n- yearly (Jährlich)\n", "example": "monthly" }, "billing_duration_amount": { "type": "number", "minimum": 0, - "description": "The duration of the billing period.", + "description": "Duration amount for billing period calculation (Abrechnungsdauer)", "example": 30 }, "renewal_duration_amount": { "type": "number", "minimum": 0, - "description": "The duration of the renewal period.", - "example": 365 + "description": "Duration of automatic contract renewal (Verlängerungsdauer)", + "example": 12 }, "renewal_duration_unit": { "type": "string", @@ -741,8 +1804,8 @@ "years" ], "default": "months", - "description": "The unit of time for the renewal period.", - "example": "years" + "description": "Unit for renewal duration (Verlängerungseinheit)", + "example": "months" }, "notice_time_amount": { "type": "number", @@ -758,29 +1821,30 @@ "years" ], "default": "months", - "description": "The unit of time for the notice period.", + "description": "Unit for notice period (Kündigungsfrist-Einheit)", "example": "months" }, "start_date": { "type": "string", - "description": "The start date of the contract.", - "example": "2021-01-01" + "format": "date", + "description": "Contract start date (Vertragsbeginn)", + "example": "2025-01-01" }, "billing_due_day": { "type": "integer", - "description": "Defines the day of the month in which the installments are due.", - "minimum": 1, + "description": "Day of the month when installments are due (Fälligkeitstag). 0 means no fixed billing day is configured.", + "minimum": 0, "maximum": 31, - "example": 2 + "example": 15 }, "installment_amount": { "type": "integer", - "description": "Set amount for installments in cents. (precision 2)", - "example": 10050 + "description": "Fixed installment amount in cents (Abschlagsbetrag in Cent).\nUses integer representation with 2 decimal precision.\nExample: 10050 = 100.50 EUR\n", + "example": 8500 }, "balance": { "type": "integer", - "description": "Current balance of the contract in cents. (precision 2)", + "description": "Current contract balance in cents (Kontostand in Cent).\nPositive value = customer owes money (Forderung).\nNegative value = customer has credit (Guthaben).\n", "example": 8990 }, "balance_currency": { @@ -791,6 +1855,7 @@ ] }, "ContractItem": { + "description": "Contract entity with all required system fields populated", "allOf": [ { "$ref": "#/components/schemas/Contract" @@ -804,17 +1869,509 @@ "_updated_at" ] }, + "InstallmentAmountValue": { + "type": "object", + "properties": { + "amount": { + "type": "integer", + "description": "Amount in cents when available or derivable.", + "example": 10050 + }, + "amount_decimal": { + "type": "string", + "description": "Decimal amount string when available or derivable.", + "example": "100.50" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + } + } + }, + "PriceContext": { + "type": "object", + "properties": { + "price_id": { + "type": "string" + }, + "price_title": { + "type": "string" + }, + "tariff_type": { + "type": "string", + "description": "Semantic tariff register for the price component, such as HT or NT." + }, + "pricing_model": { + "type": "string" + }, + "unit_amount_gross_decimal": { + "type": "string" + }, + "unit_amount_net_decimal": { + "type": "string" + }, + "before_discount_unit_amount_gross_decimal": { + "type": "string" + }, + "before_discount_unit_amount_net_decimal": { + "type": "string" + }, + "unit_discount_amount_decimal": { + "type": "string" + }, + "unit_discount_amount_net_decimal": { + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "billing_period": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "has_discount": { + "type": "boolean" + }, + "is_dynamic_tariff": { + "type": "boolean" + }, + "dynamic_tariff": { + "$ref": "#/components/schemas/DynamicTariffPriceContext" + } + } + }, + "DynamicTariffPriceContext": { + "type": "object", + "properties": { + "mode": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "average_price_decimal": { + "type": "string" + }, + "markup_amount_decimal": { + "type": "string" + }, + "markup_amount_net_decimal": { + "type": "string" + }, + "markup_amount_gross_decimal": { + "type": "string" + }, + "market_price_decimal": { + "type": "string" + }, + "market_price_currency": { + "$ref": "#/components/schemas/Currency" + }, + "market": { + "type": "string" + }, + "bidding_zone": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + }, + "ConfigurationHistoryContext": { + "type": "object", + "properties": { + "base_price": { + "$ref": "#/components/schemas/PriceContext" + }, + "base_prices": { + "type": "array", + "description": "Canonically ordered base-price components. The singular base_price remains for compatibility.", + "items": { + "$ref": "#/components/schemas/PriceContext" + } + }, + "working_price": { + "$ref": "#/components/schemas/PriceContext" + }, + "working_prices": { + "type": "array", + "description": "Canonically ordered working-price components, including separate HT and NT prices when present. The singular working_price remains for compatibility.", + "items": { + "$ref": "#/components/schemas/PriceContext" + } + } + } + }, + "ContractTariffContext": { + "type": "object", + "properties": { + "product_id": { + "type": "string" + }, + "product_title": { + "type": "string" + } + } + }, + "ContractPricingContext": { + "allOf": [ + { + "$ref": "#/components/schemas/ConfigurationHistoryContext" + }, + { + "type": "object", + "properties": { + "tariff": { + "$ref": "#/components/schemas/ContractTariffContext" + }, + "tariffs": { + "type": "array", + "description": "Canonically ordered tariff products for composite Contracts. The singular tariff is populated when exactly one tariff product exists.", + "items": { + "$ref": "#/components/schemas/ContractTariffContext" + } + } + } + } + ] + }, + "ConfigurationHistoryRowBase": { + "type": "object", + "required": [ + "event_id", + "org_id", + "entity_type", + "entity_id", + "change_type", + "schema_version", + "changed_at", + "created_at", + "source" + ], + "properties": { + "event_id": { + "type": "string" + }, + "org_id": { + "type": "string" + }, + "entity_type": { + "type": "string", + "enum": [ + "contract", + "billing_account" + ] + }, + "entity_id": { + "type": "string" + }, + "schema_version": { + "type": "integer", + "example": 1 + }, + "effective_at": { + "type": "string", + "format": "date-time" + }, + "changed_at": { + "type": "string", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "source": { + "type": "string", + "enum": [ + "portal", + "epilot", + "erp", + "system", + "api", + "external", + "journey", + "automation", + "unknown" + ] + }, + "source_label": { + "type": "string" + }, + "source_system": { + "type": "string" + }, + "source_reference": { + "type": "string" + } + } + }, + "InstallmentAmountChangedHistoryRow": { + "allOf": [ + { + "$ref": "#/components/schemas/ConfigurationHistoryRowBase" + }, + { + "type": "object", + "required": [ + "change_type", + "new_value" + ], + "properties": { + "change_type": { + "type": "string", + "enum": [ + "installment_amount_changed" + ] + }, + "previous_value": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InstallmentAmountValue" + } + ] + }, + "new_value": { + "$ref": "#/components/schemas/InstallmentAmountValue" + }, + "context": { + "$ref": "#/components/schemas/ConfigurationHistoryContext" + } + } + } + ] + }, + "ContractPricingChangedHistoryRow": { + "allOf": [ + { + "$ref": "#/components/schemas/ConfigurationHistoryRowBase" + }, + { + "type": "object", + "required": [ + "change_type", + "previous_context", + "new_context", + "changed_fields" + ], + "properties": { + "change_type": { + "type": "string", + "enum": [ + "contract_pricing_changed" + ] + }, + "previous_context": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/ContractPricingContext" + } + ] + }, + "new_context": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/ContractPricingContext" + } + ] + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "tariff", + "base_price", + "working_price", + "discount", + "dynamic_tariff_configuration" + ] + } + }, + "installment_amount": { + "description": "Installment amount effective when the pricing change was recorded.", + "allOf": [ + { + "$ref": "#/components/schemas/InstallmentAmountValue" + } + ] + } + } + } + ] + }, + "ConfigurationHistoryRow": { + "oneOf": [ + { + "$ref": "#/components/schemas/InstallmentAmountChangedHistoryRow" + }, + { + "$ref": "#/components/schemas/ContractPricingChangedHistoryRow" + } + ], + "discriminator": { + "propertyName": "change_type", + "mapping": { + "installment_amount_changed": "#/components/schemas/InstallmentAmountChangedHistoryRow", + "contract_pricing_changed": "#/components/schemas/ContractPricingChangedHistoryRow" + } + } + }, + "ConfigurationHistoryResponse": { + "type": "object", + "required": [ + "history", + "total" + ], + "properties": { + "history": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigurationHistoryRow" + } + }, + "total": { + "type": "integer" + } + } + }, + "PricingInformationBalance": { + "type": "object", + "properties": { + "amount": { + "type": "integer", + "example": 8990 + }, + "amount_decimal": { + "type": "string", + "example": "89.90" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + } + } + }, + "ContractPricingSchedule": { + "type": "object", + "required": [ + "inferred" + ], + "properties": { + "billing_due_day": { + "type": "integer", + "description": "Day of the month when installments are due. 0 means no fixed billing day is configured.", + "minimum": 0, + "maximum": 31 + }, + "billing_period": { + "type": "string", + "enum": [ + "weekly", + "monthly", + "every_quarter", + "every_6_months", + "yearly" + ] + }, + "installments_per_year": { + "type": "integer" + }, + "inferred": { + "type": "boolean" + } + } + }, + "ContractPricingInformation": { + "type": "object", + "required": [ + "entity_type", + "entity_id", + "history" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "contract" + ] + }, + "entity_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "current_installment_amount": { + "$ref": "#/components/schemas/InstallmentAmountValue" + }, + "context": { + "$ref": "#/components/schemas/ConfigurationHistoryContext" + }, + "balance": { + "$ref": "#/components/schemas/PricingInformationBalance" + }, + "schedule": { + "$ref": "#/components/schemas/ContractPricingSchedule" + }, + "pending_installment_change": { + "type": "boolean" + }, + "history": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigurationHistoryRow" + } + } + } + }, + "BillingAccountPricingInformation": { + "type": "object", + "required": [ + "entity_type", + "entity_id", + "contracts" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "billing_account" + ] + }, + "entity_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "balance": { + "$ref": "#/components/schemas/PricingInformationBalance" + }, + "contracts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContractPricingInformation" + } + } + } + }, "Balance": { "type": "object", + "description": "Customer balance summary (Kontostandübersicht).\nRepresents the aggregated balance across all contracts and orders for a customer.\n", "properties": { "balance": { "type": "integer", - "description": "Current balance of the customer in cents. (precision 2)", + "description": "Total customer balance in cents (Gesamtkontostand in Cent).\nPositive = customer owes money (Offener Betrag).\nNegative = customer has credit (Guthaben).\n", "example": 8990 }, "balance_decimal": { "type": "string", - "description": "Current balance of the customer in decimal string representation.", + "description": "Balance as decimal string for display (Kontostand als Dezimalzahl).\nFormatted with 2 decimal places.\n", "example": "89.90" }, "balance_currency": { @@ -824,7 +2381,7 @@ }, "Currency": { "type": "string", - "description": "Currency code in ISO 4217 format", + "description": "Currency code in ISO 4217 format (Währungscode).\nCommon values: EUR (Euro), CHF (Swiss Franc)\n", "example": "EUR" } } diff --git a/packages/cli/definitions/blueprint-manifest.json b/packages/cli/definitions/blueprint-manifest.json index baf517146..2a35d8e95 100644 --- a/packages/cli/definitions/blueprint-manifest.json +++ b/packages/cli/definitions/blueprint-manifest.json @@ -2,7 +2,7 @@ "openapi": "3.0.2", "info": { "title": "Blueprint Manifest API", - "version": "4.7.0", + "version": "4.8.0", "description": "Service to create and install Blueprint Manifest files" }, "tags": [ @@ -823,7 +823,7 @@ "post": { "operationId": "preInstallBlueprint", "summary": "preInstallBlueprint", - "description": "Pre-install a Blueprint based on a blueprint file", + "description": "Pre-install a Blueprint based on a blueprint file. Format-agnostic: the engine is detected from the uploaded archive, so this endpoint accepts both Terraform exports and signed V3 packages. The returned preview's `sync_engine` says which install endpoint to call next. An archive that is neither a Terraform export nor a validly signed V3 package is rejected with 400.", "tags": [ "Blueprints" ], @@ -862,6 +862,9 @@ } } } + }, + "400": { + "description": "Invalid request body, or an archive that no install engine accepts" } } } @@ -904,6 +907,7 @@ "/v2/blueprint-manifest/blueprint:install": { "post": { "operationId": "installBlueprint", + "deprecated": true, "summary": "installBlueprint", "description": "Kick off a new blueprint installation job. Returns 202 Accepted with Location header pointing to the job resource\n", "tags": [ @@ -1104,9 +1108,228 @@ } } }, + "/v2/blueprint-manifest/blueprints/{blueprint_id}/notes": { + "post": { + "operationId": "addBlueprintNote", + "summary": "addBlueprintNote", + "description": "Append an internal note to a blueprint. `id`, `created_at` and `created_by`\nare stamped server-side from the caller, so notes cannot be backdated or\nattributed to someone else. Existing notes are never modified.\n", + "tags": [ + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "required": true, + "name": "blueprint_id", + "schema": { + "$ref": "#/components/schemas/BlueprintID" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "description": "Plain-text note body. Must not be blank.", + "minLength": 1 + } + } + } + } + } + }, + "responses": { + "201": { + "description": "The created note", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintNote" + } + } + } + }, + "400": { + "description": "Blank note text", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v2/blueprint-manifest/blueprints/{blueprint_id}/notes/{note_id}": { + "patch": { + "operationId": "updateBlueprintNote", + "summary": "updateBlueprintNote", + "description": "Rewrite the text of an existing internal note. The note keeps its position in\nthe list along with `created_at` and `created_by`, so an edit cannot reassign\nauthorship; `updated_at` is stamped server-side.\n", + "tags": [ + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "required": true, + "name": "blueprint_id", + "schema": { + "$ref": "#/components/schemas/BlueprintID" + } + }, + { + "in": "path", + "required": true, + "name": "note_id", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "description": "Plain-text note body. Must not be blank.", + "minLength": 1 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The updated note", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintNote" + } + } + } + }, + "400": { + "description": "Blank note text", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Blueprint or note not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "deleteBlueprintNote", + "summary": "deleteBlueprintNote", + "description": "Remove a single internal note from a blueprint.", + "tags": [ + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "required": true, + "name": "blueprint_id", + "schema": { + "$ref": "#/components/schemas/BlueprintID" + } + }, + { + "in": "path", + "required": true, + "name": "note_id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The blueprint's remaining notes", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintNote" + } + } + } + } + } + } + }, + "404": { + "description": "Blueprint or note not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, "/v2/blueprint-manifest/blueprints/{blueprint_id}/validate": { "post": { "operationId": "validateBlueprint", + "deprecated": true, "summary": "validateBlueprint", "description": "Start a blueprint validation job. Validates Terraform for the blueprint (all types).\nReturns 202 Accepted with job_id. Poll GET /jobs/{job_id} for status, valid, and errors.\n", "tags": [ @@ -1204,6 +1427,22 @@ "destination_auth_token": { "type": "string", "description": "Auth token with access to the destination org. Required for cross-org verification when the caller token only has access to the source org. If not provided, the caller's bearer token is used for both orgs." + }, + "installation_job_id": { + "allOf": [ + { + "$ref": "#/components/schemas/BlueprintJobID" + } + ], + "description": "Optional install job this verification is checking. If omitted, the latest destination blueprint installation job is used when available." + }, + "sync_engine": { + "type": "string", + "enum": [ + "terraform", + "v3" + ], + "description": "Optional install engine hint. Usually inferred from installation_job_id." } } } @@ -1243,6 +1482,7 @@ "/v2/blueprint-manifest/blueprints/{blueprint_id}/patches:detect": { "post": { "operationId": "detectPatchChanges", + "deprecated": true, "summary": "detectPatchChanges", "description": "Detect changes between the current state of a blueprint's resources and its tfstate baseline.\nReturns field-level diffs for resources that have been modified since the blueprint was last installed/exported.\n", "tags": [ @@ -1302,6 +1542,7 @@ "/v2/blueprint-manifest/blueprints/{blueprint_id}/patches": { "post": { "operationId": "createPatch", + "deprecated": true, "summary": "createPatch", "description": "Create a new patch for a blueprint.", "tags": [ @@ -1375,6 +1616,7 @@ }, "get": { "operationId": "listPatches", + "deprecated": true, "summary": "listPatches", "description": "List all patches for a blueprint.", "tags": [ @@ -1418,6 +1660,7 @@ "/v2/blueprint-manifest/blueprints/{blueprint_id}/patches/{patch_id}": { "get": { "operationId": "getPatch", + "deprecated": true, "summary": "getPatch", "description": "Get a patch by ID, including per-org execution results.", "tags": [ @@ -1461,6 +1704,7 @@ "/v2/blueprint-manifest/blueprints/{blueprint_id}/patches/{patch_id}:apply": { "post": { "operationId": "applyPatch", + "deprecated": true, "summary": "applyPatch", "description": "Apply a patch to a single destination org.", "tags": [ @@ -1534,6 +1778,7 @@ "/v2/blueprint-manifest/blueprints/{blueprint_id}/patches/{patch_id}/orgs/{org_id}:retry": { "post": { "operationId": "retryPatchOrg", + "deprecated": true, "summary": "retryPatchOrg", "description": "Retry a failed patch execution for a specific org.", "tags": [ @@ -1602,6 +1847,7 @@ "/v2/blueprint-manifest/blueprints/{blueprint_id}:export": { "post": { "operationId": "exportBlueprint", + "deprecated": true, "summary": "exportBlueprint", "description": "Kick off a new blueprint export job. Returns 202 Accepted with Location header pointing to the job resource.\n", "tags": [ @@ -1924,6 +2170,28 @@ "items": { "$ref": "#/components/schemas/BlueprintResource" } + }, + "skipped": { + "type": "array", + "description": "Resources that were dropped during enrichment instead of being added", + "items": { + "$ref": "#/components/schemas/SkippedBlueprintResource" + } + }, + "errors": { + "type": "array", + "description": "Non-fatal dependency extraction failures encountered while enriching, deduplicated and capped. The listed resources were still added; some of their dependencies may be missing.", + "items": { + "$ref": "#/components/schemas/FormattedError" + } + }, + "total_errors": { + "type": "integer", + "description": "Total distinct dependency errors, before the cap applied to `errors`" + }, + "errors_truncated": { + "type": "boolean", + "description": "Whether `errors` omits some of the distinct errors counted by `total_errors`" } } } @@ -1949,6 +2217,21 @@ "schema": { "$ref": "#/components/schemas/BlueprintID" } + }, + { + "in": "query", + "required": false, + "name": "trigger", + "description": "What initiated the sync. Automated triggers (`pre_sync`,\n`post_revert`) are side effects of an operation the activity\nfeed already shows on its own row, so they are excluded from\nthe audit log — same policy as validate/verify.\n", + "schema": { + "type": "string", + "enum": [ + "manual", + "pre_sync", + "post_revert" + ], + "default": "manual" + } } ], "responses": { @@ -2030,6 +2313,28 @@ "items": { "$ref": "#/components/schemas/BlueprintResource" } + }, + "skipped": { + "type": "array", + "description": "Resources that were dropped during enrichment instead of being added", + "items": { + "$ref": "#/components/schemas/SkippedBlueprintResource" + } + }, + "errors": { + "type": "array", + "description": "Non-fatal dependency extraction failures encountered while enriching, deduplicated and capped. The listed resources were still added; some of their dependencies may be missing.", + "items": { + "$ref": "#/components/schemas/FormattedError" + } + }, + "total_errors": { + "type": "integer", + "description": "Total distinct dependency errors, before the cap applied to `errors`" + }, + "errors_truncated": { + "type": "boolean", + "description": "Whether `errors` omits some of the distinct errors counted by `total_errors`" } } } @@ -2245,7 +2550,7 @@ "/v2/blueprint-manifest/jobs": { "get": { "operationId": "listBlueprintJobs", - "summary": "List Blueprint Jobs", + "summary": "listBlueprintJobs", "description": "List all blueprint jobs", "tags": [ "Jobs" @@ -2279,8 +2584,8 @@ "/v2/blueprint-manifest/jobs/{job_id}": { "get": { "operationId": "getBlueprintJob", - "summary": "Get Job", - "description": "Poll current state of a job.", + "summary": "getBlueprintJob", + "description": "Poll the current state of a job. Serves both Terraform (v2) and V3-engine jobs —\ncheck `sync_engine` (`terraform` | `v3`) to tell them apart. V3 jobs additionally\nexpose live `resource_progress[]`. V3 single-install and bulk-install child jobs\nare polled here.\n", "tags": [ "Jobs" ], @@ -2311,8 +2616,8 @@ "/v2/blueprint-manifest/jobs/{job_id}:continue": { "post": { "operationId": "continueInstallationJob", - "summary": "Continue Installation Job", - "description": "Continue an installation job if it is waiting for user action.", + "summary": "continueInstallationJob", + "description": "Resume an installation job that is paused at `status: \"WAITING_USER_ACTION\"` after\nplanning. Works for both Terraform and V3 jobs. Not needed for V3 installs created\nwith `auto_apply: true` (including all bulk-install child jobs), which apply\nwithout pausing.\n\nAlso resumes a V3 install paused at `status: \"REAUTH_REQUIRED\"`, where the\ncaller's credentials could not safely cover another run. The job keeps its\n`job_id`, so resuming continues from the operation-log checkpoint rather than\nre-applying completed work. The request's bearer token becomes the destination\ncredential; for a cross-org install whose SOURCE credential also expired, send a\nfreshly scoped source token as `source_auth_token` — the destination bearer is not\na valid substitute for it.\n", "tags": [ "Jobs" ], @@ -2331,7 +2636,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BlueprintInstallationJobOptions" + "$ref": "#/components/schemas/ContinueInstallationJobRequest" } } } @@ -2350,11 +2655,11 @@ } } }, - "/v2/blueprint-manifest/jobs/{job_id}:cancel": { + "/v2/blueprint-manifest/jobs/{job_id}:retry": { "post": { - "operationId": "cancelBlueprintJob", - "summary": "Cancel Blueprint Job", - "description": "Cancel a blueprint job if it is still running.", + "operationId": "retryInstallationJob", + "summary": "retryInstallationJob", + "description": "Retry a finished V3 installation job whose status is `FAILED` or\n`PARTIAL_SUCCESS`. Starts a fresh install job (new `job_id`) with the same\nsource/destination and `auto_apply: true` — no `:continue` call is needed.\nThe plan phase re-resolves every resource: already-synced resources no-op,\nso effectively only the failed resources are re-applied.\n\nMust be called by a user of the destination org (where the job lives); the\ncaller's bearer token is used as the destination token. The plan reuses the\nmanifest persisted by the original install, so no source-org access is\nrequired. The original job's `options` (ignored resources, sync_notes) are\nreused when present; older jobs without persisted options fall back to the\ndestination blueprint's `ignored_resource_addresses`.\n", "tags": [ "Jobs" ], @@ -2369,17 +2674,71 @@ } ], "responses": { - "200": { - "description": "Blueprint job cancelled", - "content": { - "application/json": { + "202": { + "description": "Retry install job started", + "headers": { + "Location": { + "description": "Path to poll the resulting job: GET /v2/blueprint-manifest/jobs/{job_id}", "schema": { - "$ref": "#/components/schemas/BlueprintJob" + "type": "string" } } - } - } - } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "job_id": { + "$ref": "#/components/schemas/BlueprintJobID" + }, + "destination_blueprint_id": { + "$ref": "#/components/schemas/BlueprintID" + } + } + } + } + } + }, + "400": { + "description": "Job is not a retryable V3 installation job" + }, + "404": { + "description": "Job not found" + } + } + } + }, + "/v2/blueprint-manifest/jobs/{job_id}:cancel": { + "post": { + "operationId": "cancelBlueprintJob", + "summary": "cancelBlueprintJob", + "description": "Cancel a blueprint job if it is still running.", + "tags": [ + "Jobs" + ], + "parameters": [ + { + "in": "path", + "required": true, + "name": "job_id", + "schema": { + "$ref": "#/components/schemas/BlueprintJobID" + } + } + ], + "responses": { + "200": { + "description": "Blueprint job cancelled", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintJob" + } + } + } + } + } } }, "/v1/blueprints/{blueprint_id}/marketplace-listing": { @@ -2861,11 +3220,132 @@ } } }, + "/v3/blueprint-manifest/blueprints/{blueprint_id}:publish": { + "post": { + "operationId": "publishBlueprintV3", + "summary": "publishBlueprintV3", + "description": "Starts an asynchronous V3 publication. The result is a signed, portable package; poll the existing blueprint job endpoint for completion.\n\nBy default this only builds the package and stores it privately for download. Creating or\noverwriting the public marketplace listing is opt-in via `publish_to_marketplace`.\n", + "tags": [ + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "required": true, + "name": "blueprint_id", + "schema": { + "$ref": "#/components/schemas/BlueprintID" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "publish_to_marketplace": { + "type": "boolean", + "default": false, + "description": "When true, upload the package with public access and create or overwrite the\nblueprint's Webflow CMS listing. Leave unset for a download-only package build.\n" + } + } + } + } + } + }, + "responses": { + "202": { + "description": "Blueprint export job started", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "job_id" + ], + "properties": { + "job_id": { + "$ref": "#/components/schemas/BlueprintJobID" + } + } + } + } + } + } + } + } + }, + "/v3/blueprint-manifest/blueprints:pre-install": { + "post": { + "operationId": "preInstallBlueprintV3", + "summary": "preInstallBlueprintV3", + "description": "Validates a signed V3 package and returns the destination-specific resource plan used by the install UI.", + "tags": [ + "Blueprints" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "blueprint_file" + ], + "properties": { + "blueprint_file": { + "type": "string", + "description": "S3 key of a V3 blueprint package previously uploaded through uploadManifest" + }, + "source_blueprint_type": { + "type": "string", + "enum": [ + "marketplace" + ] + }, + "slug": { + "type": "string", + "description": "Marketplace slug to preserve across updates" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Destination-specific V3 blueprint preview", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintPreview" + } + } + } + }, + "400": { + "description": "Invalid request or unsupported V3 package" + }, + "404": { + "description": "Blueprint package not found" + } + } + } + }, "/v3/blueprint-manifest/blueprint:install": { "post": { "operationId": "installBlueprintV3", - "summary": "Install Blueprint V3", - "description": "Install a blueprint using the V3 engine (direct API calls, no Terraform).\nCreates resources in topological order with global ID replacement.\nSupports checkpoint-based resume on failure.\n", + "summary": "installBlueprintV3", + "description": "Install a blueprint into a single destination org using the V3 engine (direct API\ncalls, no Terraform). Creates resources in topological order with global ID\nreplacement and supports checkpoint-based resume on failure.\n\n**Lifecycle (how to drive an install to completion):**\n1. `POST /v3/blueprint-manifest/blueprint:install` returns `{ job_id }` (202).\n2. Poll the job with `GET /v2/blueprint-manifest/jobs/{job_id}` — V3 jobs are\n served by the same v2 jobs endpoints as Terraform jobs and are identified by\n `sync_engine: \"v3\"`. Watch `status` and `resource_progress[]`.\n3. If `auto_apply` is `false` (default), the job pauses at\n `status: \"WAITING_USER_ACTION\"` after planning. Resume it with\n `POST /v2/blueprint-manifest/jobs/{job_id}:continue`.\n4. If `auto_apply` is `true`, the engine applies automatically after plan +\n snapshot — no `:continue` call is needed. This is what the bulk-install worker\n uses; to install into many orgs at once prefer `POST .../bulk-installs`.\n\nFor cross-org installs, pass `destination_auth_token` (the destination org's\ntoken); reads use the caller's bearer token, writes use that token.\n", "tags": [ "Blueprints" ], @@ -2911,12 +3391,66 @@ "type": "string", "description": "Auth token for the destination org. Required for cross-org installs where the caller's token belongs to the source org. Defaults to the caller's bearer token." }, + "source_blueprint_type": { + "type": "string", + "enum": [ + "marketplace" + ] + }, "options": { "$ref": "#/components/schemas/BlueprintInstallationJobOptions" }, "slug": { "type": "string", "description": "Slug for marketplace blueprint consistency" + }, + "auto_apply": { + "type": "boolean", + "description": "When `true`, the install skips the manual plan-approval step and applies\nstraight after plan + snapshot succeed (no `:continue` call needed).\nDefaults to `false`. Used internally by the bulk-install worker.\n", + "default": false + }, + "session_credentials": { + "type": "object", + "description": "Lets the install refresh the caller's session on its own, so a run that\noutlives the ~60 minute token lifetime continues without the user present.\nHeld server-side for the job's lifetime, referenced from the state machine\nrather than embedded in it, and deleted when the job finishes.\n\nOptional. Without it an install that outruns its token pauses at\n`status: \"REAUTH_REQUIRED\"` and waits to be resumed through\n`POST /v2/blueprint-manifest/jobs/{job_id}:continue`.\n\nBounded by the refresh token itself (18 hours), not unlimited.\n", + "required": [ + "refresh_token", + "client_id" + ], + "properties": { + "refresh_token": { + "type": "string", + "format": "password", + "description": "Cognito refresh token for the caller, scoped to the source org." + }, + "client_id": { + "type": "string", + "description": "The Cognito app client the refresh token was issued for. Required because\nepilot provisions one user pool and app client per organization at\nruntime, so the service cannot derive it.\n" + }, + "pipeline_id": { + "type": "string", + "description": "Cross-org installs only. The destination credential is not a Cognito\nsession — it is minted from the refreshed source session through\nsandbox-api, so it is re-derived per continuation instead of stored.\n" + } + } + } + } + }, + "examples": { + "sameOrgPlanThenContinue": { + "summary": "Same-org install, manual approval (default)", + "value": { + "source_org_id": "source-org-id", + "source_blueprint_id": "11111111-1111-1111-1111-111111111111", + "destination_org_id": "source-org-id" + } + }, + "crossOrgAutoApply": { + "summary": "Cross-org install, applied automatically", + "value": { + "source_org_id": "source-org-id", + "source_blueprint_id": "11111111-1111-1111-1111-111111111111", + "destination_org_id": "dest-org-id", + "destination_auth_token": "destination-org-token", + "auto_apply": true } } } @@ -2928,7 +3462,7 @@ "description": "V3 install job started", "headers": { "Location": { - "description": "URL to poll the job", + "description": "Path to poll the resulting job: GET /v2/blueprint-manifest/jobs/{job_id}", "schema": { "type": "string" } @@ -2971,8 +3505,8 @@ "/v3/blueprint-manifest/blueprints/{blueprint_id}/deployments/{job_id}:restore": { "post": { "operationId": "restoreBlueprintDeploymentV3", - "summary": "Restore a specific deployment by job_id", - "description": "Roll a deployment back to its pre-install state. Two phases:\n\n 1. Upsert — re-applies the captured payloads via snapshot-api's\n `:restore` (server-side; runs config-engine.apply with captured\n target ids pre-seeded). Skipped for pure-create deployments\n whose snapshot was empty.\n 2. Delete sweep — for lineage rows of this blueprint instance not\n present in the snapshot's captured set, deletes the live\n resource via the type's adapter. Co-ownership / drift /\n no-delete-capability skip the entry with the corresponding\n reason.\n\nResolves `(blueprint_id, job_id)` to the entry in\n`Blueprint.deployments[]` and reads its `snapshot_id` and\n`destination_blueprint_id` — the caller never needs to handle\nsnapshot ids directly.\n\nAsync — returns 202 with a job id. Poll the job to track progress.\nThe per-instance lock (`installation_status === 'IN_PROGRESS'`)\nrejects concurrent installs or restores with 409.\n", + "summary": "restoreBlueprintDeploymentV3", + "description": "Roll a deployment back to its pre-install state. Two phases:\n\n 1. Upsert — re-applies the captured payloads via snapshot-api's\n `:restore` (server-side; runs config-engine.apply with captured\n target ids pre-seeded). Skipped for pure-create deployments\n whose snapshot was empty.\n 2. Delete sweep — for lineage rows of this blueprint instance not\n present in the snapshot's captured set, deletes the live\n resource via the type's adapter. Co-ownership / drift /\n no-delete-capability skip the entry with the corresponding\n reason.\n\nResolves `(blueprint_id, job_id)` to the entry in\n`Blueprint.deployments[]` and reads its `snapshot_id` and\n`destination_blueprint_id` — the caller never needs to handle\nsnapshot ids directly.\n\nAsync — returns 202 with a job id. Poll the job to track progress.\nThe restore holds its own per-instance lock (`active_restore_job_id`),\nseparate from `installation_status`. Starting a restore is rejected with\n409 when another restore already holds that lock, or when an install is\nrunning (`installation_status === 'IN_PROGRESS'`). Keeping the two locks\ndistinct keeps installs and reverts decoupled: an in-flight or failed\nrestore never overwrites `installation_status`, which keeps reflecting\nthe actual install outcome (and drives the install badge in the\nBlueprints overview) throughout the revert.\n", "tags": [ "Blueprints" ], @@ -3039,7 +3573,7 @@ "/v3/blueprint-manifest/blueprints/{blueprint_id}/deployments/{job_id}/restore-preview": { "get": { "operationId": "getRestorePreview", - "summary": "Predicted outcome of reverting a deployment", + "summary": "getRestorePreview", "description": "Computes what would happen if the user triggered a restore on this\ndeployment, without performing any writes. The forecast uses the\nsnapshot's captured resources (when present) plus the current lineage\nstate plus per-adapter gates (co-ownership, no-delete-capability,\nheuristic-match, drift when wired).\n\nIdempotent and side-effect free. Safe to call repeatedly. The result\nmay shift between calls if operators edit destination resources or\nanother blueprint adopts a shared resource in the meantime.\n", "tags": [ "Blueprints" @@ -3080,136 +3614,202 @@ } } }, - "/v3/blueprint-manifest/blueprints/{blueprint_id}/lineage": { - "get": { - "operationId": "getBlueprintLineageV3", - "summary": "Get Blueprint Lineage V3", - "description": "Returns the lineage registry entries for a blueprint's resources in the current org.\nShows the mapping between source lineage IDs and target resource IDs.\n", + "/v3/blueprint-manifest/blueprints/{blueprint_id}/deployments/{job_id}:health-check": { + "post": { + "operationId": "triggerDeploymentHealthCheckV3", + "summary": "triggerDeploymentHealthCheckV3", + "description": "Starts a read-only health scan of the resources this deployment's\nblueprint instance tracks in the destination org (see\ndocs/rfcs/RFC-org-health-check.md, Phase 0). Checks:\n\n - live readability — every lineage-tracked resource is read back\n via its adapter; tracked-but-unreadable resources are flagged.\n - referential integrity — destination payloads containing\n unreplaced source-org ids, or references to tracked resources\n that are not readable.\n - install completeness — install operations whose final attempt\n ended `failed` or `skipped`, with their rejection reasons.\n\nAsync — returns 202 immediately with a `running` report stub. Poll\nthe health-report endpoint until `status` is `completed` or\n`failed`. Never mutates any resource.\n", "tags": [ "Blueprints" ], "parameters": [ { "in": "path", - "required": true, "name": "blueprint_id", + "required": true, "schema": { "$ref": "#/components/schemas/BlueprintID" } + }, + { + "in": "path", + "name": "job_id", + "required": true, + "description": "The install job whose deployment is being checked.", + "schema": { + "$ref": "#/components/schemas/BlueprintJobID" + } } ], - "responses": { - "200": { - "description": "Lineage entries for the blueprint", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "total": { - "type": "integer" - }, - "entries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LineageEntry" - } - } + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "source_org_id": { + "type": "string", + "description": "Source/sandbox org to deep-compare entity mappings against." + }, + "source_auth_token": { + "type": "string", + "description": "Read token for the source org. Never persisted; used only for this scan." } } } } } - } - } - }, - "/v1/blueprint-manifest/uniqueness-criteria": { - "get": { - "operationId": "listUniquenessCriteria", - "summary": "listUniquenessCriteria", - "description": "List all custom uniqueness criteria configured for the caller's organization.\nThese overrides are applied during install (V2 and V3) when matching incoming\nresources against existing ones in the destination org, replacing the default\nper-resource-type field set with the caller's chosen fields (AND-combined).\n", - "tags": [ - "Uniqueness Criteria" - ], + }, "responses": { - "200": { - "description": "All configured criteria for the org", + "202": { + "description": "Health check started", + "headers": { + "Location": { + "description": "URL to poll the health report", + "schema": { + "type": "string" + } + } + }, "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "results": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UniquenessCriteria" - } - } - } + "$ref": "#/components/schemas/DeploymentHealthReport" } } } + }, + "404": { + "description": "Blueprint not found, or no deployment with the given job_id on this blueprint." } } } }, - "/v1/blueprint-manifest/uniqueness-criteria/{resource_type}": { - "parameters": [ - { - "in": "path", - "required": true, - "name": "resource_type", - "schema": { - "$ref": "#/components/schemas/UniquenessCriteriaResourceType" - } - } - ], + "/v3/blueprint-manifest/blueprints/{blueprint_id}/deployments/{job_id}/health-report": { "get": { - "operationId": "getUniquenessCriteria", - "summary": "getUniquenessCriteria", - "description": "Get the configured uniqueness criteria for a specific resource type, if any.", + "operationId": "getDeploymentHealthReportV3", + "summary": "getDeploymentHealthReportV3", + "description": "Returns the most recent health report produced for this deployment\nby the `:health-check` endpoint. Idempotent and side-effect free.\n", "tags": [ - "Uniqueness Criteria" + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "name": "blueprint_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlueprintID" + } + }, + { + "in": "path", + "name": "job_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlueprintJobID" + } + } ], "responses": { "200": { - "description": "Configured criteria for the resource type", + "description": "Latest health report", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UniquenessCriteria" + "$ref": "#/components/schemas/DeploymentHealthReport" } } } }, "404": { - "description": "No custom criteria configured (defaults will be used)" + "description": "No health report exists for this deployment yet." } } - }, - "put": { - "operationId": "putUniquenessCriteria", - "summary": "putUniquenessCriteria", - "description": "Set or replace the uniqueness criteria for a resource type. The provided fields\nmust be valid attributes on the resource's schema (the UI typically loads the\nschema to populate options). All listed fields are AND-combined during matching.\n", + } + }, + "/v3/blueprint-manifest/blueprints/{blueprint_id}/lineage": { + "get": { + "operationId": "getBlueprintLineageV3", + "summary": "getBlueprintLineageV3", + "description": "Returns the lineage registry entries for a blueprint's resources in the current org.\nShows the mapping between source lineage IDs and target resource IDs.\n", "tags": [ - "Uniqueness Criteria" + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "required": true, + "name": "blueprint_id", + "schema": { + "$ref": "#/components/schemas/BlueprintID" + } + } + ], + "responses": { + "200": { + "description": "Lineage entries for the blueprint", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LineageEntry" + } + } + } + } + } + } + } + } + } + }, + "/v3/blueprint-manifest/bulk-installs": { + "post": { + "operationId": "createBulkInstallV3", + "summary": "createBulkInstallV3", + "description": "Install one source blueprint into many destination organizations in a single\nrequest. The server schedules child V3 installs with `auto_apply=true` and caps\nactive installs at `max_concurrency`. Per-target failures are isolated and\nretryable; they do not stop the remaining targets.\n\nEach target carries its own write-only `destination_auth_token` (org-scoped).\nTokens are passed to the worker via Step Functions input only — they are never\npersisted in DynamoDB nor returned by any endpoint.\n", + "tags": [ + "Blueprints" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "type": "object", - "required": [ - "fields" - ], - "properties": { - "fields": { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } + "$ref": "#/components/schemas/BulkInstallCreateRequest" + }, + "examples": { + "twoTargets": { + "summary": "Install one blueprint into two orgs", + "value": { + "source_org_id": "source-org-id", + "source_blueprint_id": "11111111-1111-1111-1111-111111111111", + "max_concurrency": 2, + "options": { + "resources_to_ignore": [ + "epilot-product_product.foo" + ] + }, + "targets": [ + { + "destination_org_id": "dest-org-1", + "destination_auth_token": "token-for-org-1" + }, + { + "destination_org_id": "dest-org-2", + "destination_blueprint_id": "22222222-2222-2222-2222-222222222222", + "destination_auth_token": "token-for-org-2" + } + ] } } } @@ -3217,95 +3817,408 @@ } }, "responses": { - "200": { - "description": "Stored criteria", + "202": { + "description": "Bulk install accepted and scheduled", + "headers": { + "Location": { + "description": "URL to poll the bulk install", + "schema": { + "type": "string" + } + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UniquenessCriteria" + "$ref": "#/components/schemas/BulkInstall" } } } }, "400": { - "description": "Invalid resource type or field list" + "description": "Validation error — missing `source_blueprint_id`, empty/oversized `targets`\n(1–100), duplicate `destination_org_id`, missing per-target\n`destination_auth_token`, or `max_concurrency` out of range (1–5).\n", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } } } - }, - "delete": { - "operationId": "deleteUniquenessCriteria", - "summary": "deleteUniquenessCriteria", - "description": "Remove the custom criteria for a resource type, reverting to the default fields.", + } + }, + "/v3/blueprint-manifest/bulk-installs/{bulk_job_id}": { + "get": { + "operationId": "getBulkInstallV3", + "summary": "getBulkInstallV3", + "description": "Returns the bulk install parent with aggregate status and counts. Scoped by the\ncaller org as `source_org_id`. Target rows are not included — use the targets\nendpoint to page through them.\n", "tags": [ - "Uniqueness Criteria" + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "name": "bulk_job_id", + "required": true, + "schema": { + "type": "string" + } + } ], "responses": { - "204": { - "description": "Criteria deleted (defaults will be used)" + "200": { + "description": "Bulk install parent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkInstall" + } + } + } + }, + "404": { + "description": "Bulk install not found for the caller org" } } } - } - }, - "components": { - "schemas": { - "UniquenessCriteriaResourceType": { - "type": "string", - "description": "Resource type for which custom uniqueness criteria can be configured.", - "enum": [ - "emailtemplate", - "product", - "price", - "tax", - "coupon", - "product_recommendation", - "file", - "document_template", - "schema", - "taxonomy", - "notification_template", - "family", - "permission", - "journey" - ] - }, - "UniquenessCriteria": { - "type": "object", - "required": [ - "org_id", - "resource_type", - "fields", - "updated_at" + }, + "/v3/blueprint-manifest/bulk-installs/{bulk_job_id}/targets": { + "get": { + "operationId": "listBulkInstallTargetsV3", + "summary": "listBulkInstallTargetsV3", + "description": "Pages through the bulk install's target rows. Each row hydrates its latest child\ninstall job (`job_ids.at(-1)`) with the standard V3 job shape (`events[]`,\n`resource_progress[]`) so callers can inspect per-resource progress and errors.\n", + "tags": [ + "Blueprints" ], - "properties": { - "org_id": { - "type": "string" + "parameters": [ + { + "in": "path", + "name": "bulk_job_id", + "required": true, + "schema": { + "type": "string" + } }, - "resource_type": { - "$ref": "#/components/schemas/UniquenessCriteriaResourceType" + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } }, - "fields": { - "type": "array", - "minItems": 1, - "items": { + { + "in": "query", + "name": "cursor", + "required": false, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "A page of target rows", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkInstallTargetList" + } + } + } }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "updated_by": { - "type": "string" + "404": { + "description": "Bulk install not found for the caller org" } } - }, - "LineageEntry": { - "type": "object", - "properties": { - "lineage_id": { - "type": "string", - "description": "Immutable resource identity across orgs" + } + }, + "/v3/blueprint-manifest/bulk-installs/{bulk_job_id}/targets/{destination_org_id}:retry": { + "post": { + "operationId": "retryBulkInstallTargetV3", + "summary": "retryBulkInstallTargetV3", + "description": "Retries a single failed target. Allowed only for `FAILED` and `PARTIAL_SUCCESS`\ntargets. Starts a new child install with `auto_apply=true`, appends its job id to\n`job_ids`, and reuses the same target row. Only the destination auth token may be\nsupplied for the new attempt; source/destination identifiers are immutable.\n", + "tags": [ + "Blueprints" + ], + "parameters": [ + { + "in": "path", + "name": "bulk_job_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "destination_org_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "destination_auth_token" + ], + "properties": { + "destination_auth_token": { + "type": "string", + "description": "Write-only auth token for the destination org used for the new attempt." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Updated target row with hydrated latest child job", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkInstallTarget" + } + } + } + }, + "400": { + "description": "Target is not in a retryable (`FAILED`/`PARTIAL_SUCCESS`) state." + }, + "404": { + "description": "Bulk install or target not found for the caller org." + }, + "409": { + "description": "The target was concurrently claimed by another retry (already in progress)." + } + } + } + }, + "/v1/blueprint-manifest/uniqueness-criteria": { + "get": { + "operationId": "listUniquenessCriteria", + "summary": "listUniquenessCriteria", + "description": "List all custom uniqueness criteria configured for the caller's organization.\nThese overrides are applied during install (V2 and V3) when matching incoming\nresources against existing ones in the destination org, replacing the default\nper-resource-type field set with the caller's chosen fields (AND-combined).\n", + "tags": [ + "Uniqueness Criteria" + ], + "responses": { + "200": { + "description": "All configured criteria for the org", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UniquenessCriteria" + } + }, + "defaults": { + "type": "object", + "description": "Built-in default uniqueness fields per resource type, as used by\nthe install engines when no custom criteria are configured.\n", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "readonly_types": { + "type": "array", + "description": "Resource types whose criteria are fixed by epilot and cannot be\ncustomized (writes are rejected). Shown read-only in the UI.\n", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/v1/blueprint-manifest/uniqueness-criteria/{resource_type}": { + "parameters": [ + { + "in": "path", + "required": true, + "name": "resource_type", + "schema": { + "$ref": "#/components/schemas/UniquenessCriteriaResourceType" + } + } + ], + "get": { + "operationId": "getUniquenessCriteria", + "summary": "getUniquenessCriteria", + "description": "Get the configured uniqueness criteria for a specific resource type, if any.", + "tags": [ + "Uniqueness Criteria" + ], + "responses": { + "200": { + "description": "Configured criteria for the resource type", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteria" + } + } + } + }, + "404": { + "description": "No custom criteria configured (defaults will be used)" + } + } + }, + "put": { + "operationId": "putUniquenessCriteria", + "summary": "putUniquenessCriteria", + "description": "Set or replace the uniqueness criteria for a resource type. The provided fields\nmust be valid attributes on the resource's schema (the UI typically loads the\nschema to populate options). All listed fields are AND-combined during matching.\n", + "tags": [ + "Uniqueness Criteria" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "fields" + ], + "properties": { + "fields": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "propagated_to": { + "type": "array", + "description": "Org IDs this rule was also applied to (the UI's \"Also apply to\"\nselection). Stored so the selection survives reloads; each target\norg still holds its own criteria row.\n", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Stored criteria", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteria" + } + } + } + }, + "400": { + "description": "Invalid resource type or field list" + } + } + }, + "delete": { + "operationId": "deleteUniquenessCriteria", + "summary": "deleteUniquenessCriteria", + "description": "Remove the custom criteria for a resource type, reverting to the default fields.", + "tags": [ + "Uniqueness Criteria" + ], + "responses": { + "204": { + "description": "Criteria deleted (defaults will be used)" + } + } + } + } + }, + "components": { + "schemas": { + "UniquenessCriteriaResourceType": { + "type": "string", + "description": "Resource type for which custom uniqueness criteria can be configured.", + "enum": [ + "emailtemplate", + "product", + "price", + "tax", + "coupon", + "product_recommendation", + "file", + "document_template", + "notification_template", + "journey" + ] + }, + "UniquenessCriteria": { + "type": "object", + "required": [ + "org_id", + "resource_type", + "fields", + "updated_at" + ], + "properties": { + "org_id": { + "type": "string" + }, + "resource_type": { + "$ref": "#/components/schemas/UniquenessCriteriaResourceType" + }, + "fields": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "propagated_to": { + "type": "array", + "items": { + "type": "string" + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "updated_by": { + "type": "string" + } + } + }, + "LineageEntry": { + "type": "object", + "properties": { + "lineage_id": { + "type": "string", + "description": "Immutable resource identity across orgs" }, "target_id": { "type": "string", @@ -3504,17 +4417,50 @@ } ] }, - "BlueprintID": { - "type": "string", - "description": "ID of a blueprint", - "example": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341" - }, - "BlueprintResourceID": { - "type": "string", - "description": "ID of a blueprint resource", - "example": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341" - }, - "CommonBlueprintFields": { + "BlueprintNote": { + "type": "object", + "description": "A single internal note on a blueprint.", + "required": [ + "id", + "text", + "created_at" + ], + "properties": { + "id": { + "type": "string", + "description": "Server-generated note id.", + "example": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40" + }, + "text": { + "type": "string", + "description": "Plain-text note body.", + "example": "Adjusted the meter-reading journey for the §14a rollout." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Set the first time the note text is edited." + }, + "created_by": { + "$ref": "#/components/schemas/CallerIdentity" + } + } + }, + "BlueprintID": { + "type": "string", + "description": "ID of a blueprint", + "example": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341" + }, + "BlueprintResourceID": { + "type": "string", + "description": "ID of a blueprint resource", + "example": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341" + }, + "CommonBlueprintFields": { "type": "object", "properties": { "id": { @@ -3541,6 +4487,13 @@ } } }, + "notes": { + "type": "array", + "description": "Internal collaboration notes, oldest first. Each entry is stamped with\nits author and creation time server-side; an edit rewrites only `text`\nand stamps `updated_at`, so the history of who noted what stays intact.\nWritten via `addBlueprintNote` / `updateBlueprintNote` /\n`deleteBlueprintNote`, not by `updateBlueprint`.\n\nAvailable on every blueprint including marketplace ones (whose\n`description` is read-only), and never included in the published\nmarketplace package — see `buildMetadata` in\n`services/blueprint-v3/published-blueprint.ts`. Carried to a\ndestination org only when an install/sync passes\n`options.sync_notes: true`.\n", + "items": { + "$ref": "#/components/schemas/BlueprintNote" + } + }, "version": { "type": "string" }, @@ -3569,6 +4522,14 @@ "type": "string", "format": "date-time" }, + "performed_by": { + "allOf": [ + { + "$ref": "#/components/schemas/CallerIdentity" + } + ], + "description": "Who actually ran this synchronization — the caller of the install job.\nAbsent on deployments recorded before this field existed; consumers\nshould fall back to the blueprint's `updated_by` for those.\n" + }, "note": { "type": "string", "description": "User-provided note about this synchronization" @@ -3708,6 +4669,17 @@ "FAILED" ] }, + "active_restore_job_id": { + "type": "string", + "nullable": true, + "description": "ID of the restore (revert) job currently holding a lock on this\nblueprint instance's resources, or null/absent when no revert is\nrunning. Deliberately separate from `installation_status`: installs\nand reverts stay decoupled, so a revert must not overwrite install\nhealth — `installation_status` keeps reflecting the actual install\noutcome (and drives the install badge in the Blueprints overview)\nfor the duration of the revert.\n" + }, + "active_restore_started_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "ISO timestamp of when the current restore lock\n(`active_restore_job_id`) was acquired, or null/absent when no revert\nis running. Written and cleared alongside `active_restore_job_id`.\nLets the lock self-expire: a lock older than the staleness threshold\n(whose job never completed the sweep, e.g. the state machine was\naborted) is treated as stale so a new revert can proceed instead of\nbeing blocked forever.\n" + }, "created_at": { "type": "string", "format": "date-time" @@ -3830,6 +4802,31 @@ } } }, + "SkippedBlueprintResource": { + "type": "object", + "description": "A resource that was requested (or discovered as a dependency) but was not\nadded to the Blueprint. Reasons are stable machine-readable codes.\n", + "required": [ + "id", + "type", + "reason" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/BlueprintResourceID" + }, + "type": { + "$ref": "#/components/schemas/ResourceNodeType" + }, + "reason": { + "type": "string", + "enum": [ + "not_found", + "source_validation_failed", + "enrichment_failed" + ] + } + } + }, "BlueprintResource": { "type": "object", "properties": { @@ -3892,7 +4889,8 @@ "internal-update", "no-op", "delete", - "ignored" + "ignored", + "error" ] } }, @@ -3945,6 +4943,14 @@ "file" ] }, + "sync_engine": { + "type": "string", + "description": "Engine that must install this preview, detected from the uploaded archive's own format (never from a feature flag or request parameter). `v3` is only reported for a correctly shaped and validly signed V3 package. Clients must install a `v3` preview through `POST /v3/blueprint-manifest/blueprint:install` and a `terraform` preview through `POST /v2/blueprint-manifest/blueprints:install`. Absent only on previews created before format detection shipped; treat an absent value as `terraform`.", + "enum": [ + "terraform", + "v3" + ] + }, "blueprint_file_s3_key": { "type": "string", "description": "S3 key of the blueprint zip file" @@ -4355,11 +5361,15 @@ "$ref": "#/components/schemas/V3ResourceProgressEntry" } }, + "options": { + "$ref": "#/components/schemas/BlueprintInstallationJobOptions" + }, "status": { "type": "string", "enum": [ "IN_PROGRESS", "WAITING_USER_ACTION", + "REAUTH_REQUIRED", "CANCELED", "SUCCESS", "PARTIAL_SUCCESS", @@ -4370,162 +5380,743 @@ } ] }, + "BulkInstallStatus": { + "type": "string", + "description": "Aggregate status for a bulk install or one of its targets.\n- `QUEUED`: not started yet\n- `IN_PROGRESS`: at least one target queued/in-progress, not all done\n- `SUCCESS`: all targets succeeded\n- `PARTIAL_SUCCESS`: all targets terminal with a mix of success/partial/failure\n- `FAILED`: all targets terminal and none succeeded or partially succeeded\n", + "enum": [ + "QUEUED", + "IN_PROGRESS", + "SUCCESS", + "PARTIAL_SUCCESS", + "FAILED" + ] + }, + "BulkInstallCounts": { + "type": "object", + "description": "Tally of target rows by status. Recomputed from target rows on each transition.", + "properties": { + "queued": { + "type": "integer" + }, + "in_progress": { + "type": "integer" + }, + "success": { + "type": "integer" + }, + "partial_success": { + "type": "integer" + }, + "failed": { + "type": "integer" + } + } + }, + "BulkInstall": { + "type": "object", + "description": "Bulk install parent. Never carries target auth tokens.", + "properties": { + "bulk_job_id": { + "type": "string" + }, + "source_org_id": { + "type": "string" + }, + "source_blueprint_id": { + "$ref": "#/components/schemas/BlueprintID" + }, + "status": { + "$ref": "#/components/schemas/BulkInstallStatus" + }, + "target_count": { + "type": "integer" + }, + "max_concurrency": { + "type": "integer" + }, + "counts": { + "$ref": "#/components/schemas/BulkInstallCounts" + }, + "slug": { + "type": "string" + }, + "options": { + "$ref": "#/components/schemas/BlueprintInstallationJobOptions" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BulkInstallTarget": { + "type": "object", + "description": "A single destination of a bulk install. `job` is the hydrated latest child\ninstall job derived from `job_ids.at(-1)`. Auth tokens are never stored or returned.\n", + "properties": { + "bulk_job_id": { + "type": "string" + }, + "destination_org_id": { + "type": "string" + }, + "destination_blueprint_id": { + "$ref": "#/components/schemas/BlueprintID" + }, + "status": { + "$ref": "#/components/schemas/BulkInstallStatus" + }, + "job_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintJobID" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "job": { + "allOf": [ + { + "$ref": "#/components/schemas/BlueprintInstallationJob" + } + ], + "nullable": true, + "description": "The hydrated latest child install job (`job_ids.at(-1)`), when present." + } + } + }, + "BulkInstallTargetList": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkInstallTarget" + } + }, + "next_cursor": { + "type": "string", + "description": "Opaque cursor for the next page. Absent on the last page." + } + } + }, + "BulkInstallTargetInput": { + "type": "object", + "required": [ + "destination_org_id", + "destination_auth_token" + ], + "properties": { + "destination_org_id": { + "type": "string" + }, + "destination_blueprint_id": { + "$ref": "#/components/schemas/BlueprintID" + }, + "destination_auth_token": { + "type": "string", + "description": "Write-only org-scoped token for the destination org. Never persisted or returned." + } + } + }, + "BulkInstallCreateRequest": { + "type": "object", + "required": [ + "source_blueprint_id", + "targets" + ], + "properties": { + "source_org_id": { + "type": "string", + "description": "The org that owns the source blueprint. Optional; defaults to the caller org and,\nif provided, must equal it — source reads use the caller's token, and the bulk job\nis owned/polled by the caller org. A different value is rejected with 400.\n" + }, + "source_blueprint_id": { + "$ref": "#/components/schemas/BlueprintID" + }, + "max_concurrency": { + "type": "integer", + "minimum": 1, + "maximum": 5, + "default": 2, + "description": "Maximum number of concurrently active child installs." + }, + "slug": { + "type": "string" + }, + "options": { + "$ref": "#/components/schemas/BlueprintInstallationJobOptions" + }, + "targets": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "items": { + "$ref": "#/components/schemas/BulkInstallTargetInput" + } + } + } + }, "BlueprintRestoreJob": { "allOf": [ { "$ref": "#/components/schemas/CommonBlueprintJobFields" }, - { + { + "type": "object", + "properties": { + "job_type": { + "type": "string", + "enum": [ + "restore" + ] + }, + "destination_blueprint_id": { + "$ref": "#/components/schemas/BlueprintID" + }, + "destination_org_id": { + "type": "string" + }, + "install_job_id": { + "type": "string", + "nullable": true, + "description": "The install job whose deployment is being reverted. Maps back\nto the entry in `Blueprint.deployments[]`.\n" + }, + "snapshot_id": { + "type": "string", + "nullable": true, + "description": "The snapshot driving Phase 1 of the restore. Null for sweep-only\nrestores (pure-create deployments with no captured manifest).\n" + }, + "sync_engine": { + "type": "string", + "enum": [ + "v3" + ] + }, + "status": { + "type": "string", + "enum": [ + "IN_PROGRESS", + "SUCCESS", + "PARTIAL_SUCCESS", + "FAILED" + ] + }, + "restore_result": { + "nullable": true, + "description": "Absent while the job is still IN_PROGRESS.", + "allOf": [ + { + "$ref": "#/components/schemas/RestoreOutcome" + } + ] + } + } + } + ] + }, + "V3ResourceProgressEntry": { + "type": "object", + "required": [ + "lineage_id", + "type", + "address", + "status" + ], + "properties": { + "lineage_id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "address": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "in_progress", + "done", + "failed", + "skipped" + ] + }, + "target_id": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "error_code": { + "type": "string", + "description": "Stable machine code for a failed resource, when config-engine threw a\ntyped error. Keys a translation in the Blueprints UI; `error_message`\nis the English fallback when absent or unrecognised.\n" + }, + "error_data": { + "type": "object", + "additionalProperties": true, + "description": "Interpolation values for the translated `error_code` message." + } + } + }, + "RestoreOutcomeItem": { + "type": "object", + "required": [ + "lineage_id", + "type", + "action" + ], + "properties": { + "lineage_id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "name": { + "type": "string", + "nullable": true + }, + "target_id": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "description": "On `restore-preview`: the action the restore would take.\nOn `restore_result`: the action that was applied.\n`failed` only appears on `restore_result`.\n", + "enum": [ + "restore", + "delete", + "skip", + "failed" + ] + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Only set when `action == skip`.", + "enum": [ + "modified", + "delete_unsupported", + "heuristic_match", + "co_owned", + "dependency_protected", + "already_deleted", + "non_revertible" + ] + }, + "last_synced_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Only set when `reason == modified`. From the lineage row's last install write." + }, + "current_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Only set when `reason == modified`. From the destination resource's current state." + }, + "error_message": { + "type": "string", + "nullable": true, + "description": "Only set when `action == failed`." + }, + "is_hidden": { + "type": "boolean", + "description": "Mirrors the install manifest's `is_hidden` for this resource —\nhelper resources (entity mappings, datasources, flow-template\nautomations) the UI hides on every other resources view. Absent\non rows from legacy installs without a persisted manifest.\n" + }, + "co_owned_by": { + "type": "array", + "description": "Only set when `reason == co_owned`. The other live blueprint\ninstances that still own this resource's lineage row — the\n\"another sync\" the skip refers to.\n", + "items": { + "type": "object", + "required": [ + "blueprint_id" + ], + "properties": { + "blueprint_id": { + "type": "string" + }, + "title": { + "type": "string", + "nullable": true + } + } + } + }, + "protected_by": { + "type": "array", + "description": "Only set when `reason == dependency_protected`. The surviving\nresource(s) whose 'hard' reference to this one is why it wasn't\ndeleted (e.g. a skipped Journey still triggering this Automation).\n", + "items": { + "type": "object", + "required": [ + "lineage_id", + "type" + ], + "properties": { + "lineage_id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "target_id": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "HealthFinding": { + "type": "object", + "required": [ + "check_id", + "code", + "severity", + "resource_type", + "lineage_id", + "message" + ], + "properties": { + "check_id": { + "type": "string", + "enum": [ + "live_readability", + "referential_integrity", + "install_completeness", + "schema_consistency", + "mapping_integrity", + "execution_readiness", + "catalog_hygiene", + "spec_conformance" + ] + }, + "code": { + "type": "string", + "enum": [ + "missing_in_destination", + "fetch_error", + "no_readback", + "unreplaced_source_reference", + "broken_internal_reference", + "dropped_at_install", + "orphaned_group_reference", + "uuid_group_label", + "unreachable_attribute_purpose", + "duplicate_attribute_name", + "duplicate_headline", + "broken_mapping_reference", + "stale_mapping_version", + "mapping_slot_mismatch", + "mapping_parity_mismatch", + "stale_lineage", + "broken_trigger_reference", + "broken_action_reference", + "broken_workflow_step", + "invalid_mapping_target", + "invalid_mapping_source", + "invalid_mapping_version", + "unknown_mapping_attribute", + "workflow_edge_limit", + "dead_purpose_reference", + "broken_portal_reference", + "orphaned_portal_block", + "broken_closing_reason", + "broken_journey_settings", + "unresolved_assignee", + "current_write_invalid", + "duplicate_live_resource", + "missing_file_content", + "file_etag_mismatch", + "incomplete_webhook", + "broken_template_reference", + "orphaned_price", + "duplicate_price", + "inactive_price_in_use", + "cross_org_reference" + ] + }, + "severity": { + "type": "string", + "enum": [ + "error", + "warning", + "info" + ] + }, + "resource_type": { + "type": "string" + }, + "lineage_id": { + "type": "string" + }, + "target_id": { + "type": "string" + }, + "resource_name": { + "type": "string", + "description": "Human-readable name of the affected resource" + }, + "message": { + "type": "string" + }, + "verdict": { + "type": "string", + "description": "Mechanical root-cause classification and suggested fix, when determinable" + }, + "evidence": { "type": "object", + "description": "Check-specific evidence, e.g. the JSON path and referenced id of a broken reference.", "properties": { - "job_type": { - "type": "string", - "enum": [ - "restore" - ] + "path": { + "type": "string" }, - "destination_blueprint_id": { - "$ref": "#/components/schemas/BlueprintID" + "referenced_id": { + "type": "string" }, - "destination_org_id": { + "referenced_type": { "type": "string" }, - "install_job_id": { + "referenced_name": { "type": "string", - "nullable": true, - "description": "The install job whose deployment is being reverted. Maps back\nto the entry in `Blueprint.deployments[]`.\n" + "description": "Human-readable name or logical slug of the referenced source resource" }, - "snapshot_id": { + "referenced_lineage_id": { "type": "string", - "nullable": true, - "description": "The snapshot driving Phase 1 of the restore. Null for sweep-only\nrestores (pure-create deployments with no captured manifest).\n" + "description": "Logical source lineage identity of the referenced resource" }, - "sync_engine": { + "expected_target_id": { "type": "string", - "enum": [ - "v3" - ] + "description": "Destination id that should have replaced the source reference" }, - "status": { + "reference_kind": { "type": "string", - "enum": [ - "IN_PROGRESS", - "SUCCESS", - "PARTIAL_SUCCESS", - "FAILED" - ] + "description": "Human-readable description of the exact field carrying the reference" }, - "restore_result": { - "nullable": true, - "description": "Absent while the job is still IN_PROGRESS.", - "allOf": [ - { - "$ref": "#/components/schemas/RestoreOutcome" - } - ] + "operation": { + "type": "string" + }, + "rejection_reason": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "response_status": { + "type": "integer" } } } - ] + } }, - "V3ResourceProgressEntry": { + "HealthCheckCoverage": { "type": "object", "required": [ - "lineage_id", - "type", - "address", - "status" + "check_id", + "status", + "scope", + "resources_considered", + "details" ], "properties": { - "lineage_id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "address": { - "type": "string" - }, - "name": { - "type": "string" + "check_id": { + "type": "string", + "enum": [ + "live_readability", + "referential_integrity", + "install_completeness", + "schema_consistency", + "mapping_integrity", + "execution_readiness", + "spec_conformance" + ] }, "status": { "type": "string", "enum": [ - "pending", - "in_progress", - "done", - "failed", - "skipped" + "completed", + "partial", + "skipped", + "not_applicable" ] }, - "target_id": { - "type": "string" + "scope": { + "type": "string", + "enum": [ + "tracked_resources", + "org_wide", + "source_comparison" + ] }, - "error_message": { - "type": "string" + "resources_considered": { + "type": "integer" + }, + "details": { + "type": "array", + "items": { + "type": "string" + } } } }, - "RestoreOutcomeItem": { + "HealthResourceTypeCoverage": { "type": "object", "required": [ - "lineage_id", - "type", - "action" + "resource_type", + "tracked", + "readable_by_lineage", + "missing_by_lineage", + "read_errors", + "unchecked", + "specialized_checks", + "known_blind_spots" ], "properties": { - "lineage_id": { + "resource_type": { "type": "string" }, - "type": { - "type": "string" + "tracked": { + "type": "integer" }, - "name": { - "type": "string", - "nullable": true + "readable_by_lineage": { + "type": "integer" }, - "target_id": { - "type": "string", - "nullable": true + "missing_by_lineage": { + "type": "integer" }, - "action": { + "read_errors": { + "type": "integer" + }, + "unchecked": { + "type": "integer" + }, + "specialized_checks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "live_readability", + "referential_integrity", + "install_completeness", + "schema_consistency", + "mapping_integrity", + "execution_readiness", + "spec_conformance" + ] + } + }, + "known_blind_spots": { + "type": "array", + "description": "Type-specific contracts the current scanner explicitly does not claim to validate.", + "items": { + "type": "string" + } + } + } + }, + "HealthScanCoverage": { + "type": "object", + "required": [ + "checks", + "resource_types" + ], + "properties": { + "checks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HealthCheckCoverage" + } + }, + "resource_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HealthResourceTypeCoverage" + } + } + } + }, + "DeploymentHealthReport": { + "type": "object", + "required": [ + "status", + "job_id", + "blueprint_instance_id", + "destination_org_id", + "generated_at" + ], + "properties": { + "status": { "type": "string", - "description": "On `restore-preview`: the action the restore would take.\nOn `restore_result`: the action that was applied.\n`failed` only appears on `restore_result`.\n", "enum": [ - "restore", - "delete", - "skip", + "running", + "completed", "failed" ] }, - "reason": { - "type": "string", - "nullable": true, - "description": "Only set when `action == skip`.", - "enum": [ - "modified", - "co_owned", - "delete_unsupported", - "heuristic_match" - ] + "job_id": { + "$ref": "#/components/schemas/BlueprintJobID" }, - "last_synced_at": { + "blueprint_instance_id": { + "$ref": "#/components/schemas/BlueprintID" + }, + "destination_org_id": { + "type": "string" + }, + "generated_at": { "type": "string", - "format": "date-time", - "nullable": true, - "description": "Only set when `reason == modified`. From the lineage row's last install write." + "format": "date-time" }, - "current_updated_at": { + "html_url": { "type": "string", - "format": "date-time", - "nullable": true, - "description": "Only set when `reason == modified`. From the destination resource's current state." + "description": "Presigned link to the rendered HTML report (present once the check completed)" }, - "error_message": { + "summary": { + "type": "object", + "properties": { + "resources_scanned": { + "type": "integer" + }, + "unchecked": { + "type": "integer" + }, + "errors": { + "type": "integer" + }, + "warnings": { + "type": "integer" + }, + "infos": { + "type": "integer" + } + } + }, + "findings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HealthFinding" + } + }, + "coverage": { + "$ref": "#/components/schemas/HealthScanCoverage" + }, + "error": { "type": "string", - "nullable": true, - "description": "Only set when `action == failed`." + "description": "Failure reason when status is `failed`." } } }, @@ -4542,6 +6133,10 @@ "items": { "$ref": "#/components/schemas/RestoreOutcomeItem" } + }, + "has_effective_changes": { + "type": "boolean", + "description": "`true` iff at least one entry in `resources` has an effective\naction (`action` is `restore` or `delete`). `false` when every\nresource would be skipped (all `skip` / `failed`). Consumers\ncan gate the \"Revert sync\" confirm button on this — if false,\nexecuting the revert is a no-op.\n" } } }, @@ -4674,6 +6269,22 @@ "destination_blueprint_id": { "$ref": "#/components/schemas/BlueprintID" }, + "installation_job_id": { + "allOf": [ + { + "$ref": "#/components/schemas/BlueprintJobID" + } + ], + "description": "Install job this verification is checking, when known." + }, + "sync_engine": { + "type": "string", + "enum": [ + "terraform", + "v3" + ], + "description": "Install engine used by the linked installation job, when known." + }, "status": { "type": "string", "enum": [ @@ -4731,6 +6342,16 @@ "destination_blueprint_id": { "$ref": "#/components/schemas/BlueprintID" }, + "installation_job_id": { + "$ref": "#/components/schemas/BlueprintJobID" + }, + "sync_engine": { + "type": "string", + "enum": [ + "terraform", + "v3" + ] + }, "summary": { "$ref": "#/components/schemas/VerificationSummary" } @@ -4768,9 +6389,15 @@ "source_resource_id": { "type": "string" }, + "source_resource_address": { + "type": "string" + }, "destination_resource_id": { "type": "string" }, + "destination_resource_address": { + "type": "string" + }, "status": { "type": "string", "enum": [ @@ -4780,6 +6407,32 @@ "fetch_error" ] }, + "failure_context": { + "type": "string", + "enum": [ + "depends_on_failed_resource", + "may_be_caused_by_failed_dependency" + ], + "description": "Explains whether this result is likely downstream of another failed resource." + }, + "failed_dependency_resource_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "failed_dependency_resource_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "failed_dependency_addresses": { + "type": "array", + "items": { + "type": "string" + } + }, "field_diffs": { "type": "array", "items": { @@ -4860,9 +6513,31 @@ "items": { "type": "string" } + }, + "sync_notes": { + "description": "When `true`, the source blueprint's `notes` overwrite the destination\nblueprint's `notes`. Defaults to `false`, which leaves the\ndestination's notes untouched.\n", + "type": "boolean", + "default": false } } }, + "ContinueInstallationJobRequest": { + "allOf": [ + { + "$ref": "#/components/schemas/BlueprintInstallationJobOptions" + }, + { + "type": "object", + "properties": { + "source_auth_token": { + "description": "A freshly issued bearer for the SOURCE organization, used only when resuming a\njob paused at `REAUTH_REQUIRED` whose source credential also expired. Applies to\ncross-org installs: apply-time reads such as the authenticated File API fallback\nfor private source files run against the source org, so the destination bearer\ncannot stand in for them. Omit it to keep the source token the job already has.\n", + "type": "string", + "format": "password" + } + } + } + ] + }, "ManifestID": { "type": "string", "description": "ID of an imported / installed manifest", @@ -4935,6 +6610,7 @@ "PENDING", "STARTED", "WAITING_USER_ACTION", + "REAUTH_REQUIRED", "CANCELED", "IN_PROGRESS", "SUCCESS", @@ -4968,6 +6644,7 @@ "webhook", "integration", "dashboard", + "insight", "custom_variable", "usergroup", "saved_view", diff --git a/packages/cli/definitions/calendar.json b/packages/cli/definitions/calendar.json index 3107b7468..39e6720b7 100644 --- a/packages/cli/definitions/calendar.json +++ b/packages/cli/definitions/calendar.json @@ -3,16 +3,24 @@ "info": { "title": "Calendar API", "version": "0.1.0", - "description": "epilot's calendar API.\n\nExposes calendars and events as first-class epilot resources.\nToday, data is sourced from external providers (Microsoft 365, Google Calendar) synced into epilot.\nIn the future, epilot will also host native calendars created directly in the platform.\n\nThe provider is an implementation detail surfaced only through each resource's `source` field.\nConsumers interact with calendars and events through the same API regardless of origin." + "description": "epilot's calendar API.\n\nExposes calendars and events as first-class epilot resources." }, "tags": [ + { + "name": "Absence", + "description": "User absence from calendar events and absence adjustments" + }, { "name": "Calendars", "description": "User and organization calendars" }, { - "name": "Events", + "name": "Calendar Events", "description": "Calendar events" + }, + { + "name": "Working Hours", + "description": "Recurring weekly working hours of users" } ], "servers": [ @@ -29,94 +37,326 @@ } ], "paths": { - "/v1/calendar": { + "/v1/calendar/absence/users": { "get": { - "operationId": "listCalendars", - "summary": "listCalendars", - "description": "List calendars visible to the caller.", + "operationId": "listUsersAbsence", + "summary": "listUsersAbsence", + "description": "List organization users with known absence metadata in the requested time window.", "tags": [ - "Calendars" + "Absence" ], "parameters": [ { "in": "query", - "name": "size", + "name": "from", + "description": "Start of the time window (inclusive). Maximum window: 31 days.", + "schema": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive). Maximum window: 31 days." + }, + "required": true + }, + { + "in": "query", + "name": "to", + "description": "End of the time window (exclusive). Must be after from.", + "schema": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "required": true + }, + { + "in": "query", + "name": "include_busy", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "schema": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + } + }, + { + "in": "query", + "name": "working_hours_granularity", + "description": "At what granularity working hours contribute to absence. \"time\" (default): all time outside a user's working windows counts as absent. \"day\": only full days with no working windows count as absent (whole-day granularity, used by thread assignment). Users without a working-hours record never produce working-hours intervals.", + "schema": { + "type": "string", + "enum": [ + "time", + "day" + ], + "description": "At what granularity working hours contribute to absence. \"time\" (default): all time outside a user's working windows counts as absent. \"day\": only full days with no working windows count as absent (whole-day granularity, used by thread assignment). Users without a working-hours record never produce working-hours intervals.", + "default": "time" + } + }, + { + "in": "query", + "name": "query", + "description": "Optional user directory search query.", + "schema": { + "type": "string", + "description": "Optional user directory search query." + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum users to return.", "schema": { "type": "integer", "minimum": 1, - "maximum": 250 + "maximum": 100, + "description": "Maximum users to return." } }, { "in": "query", - "name": "cursor", - "description": "Opaque cursor from a previous response", + "name": "offset", + "description": "User directory offset.", "schema": { - "type": "string", - "description": "Opaque cursor from a previous response" + "type": "integer", + "minimum": 0, + "description": "User directory offset." } } ], "responses": { "200": { - "description": "List of calendars", + "description": "Organization users absence", "content": { "application/json": { "schema": { "type": "object", "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "hits": { + "type": "integer", + "minimum": 0 + }, "results": { "type": "array", "items": { - "$ref": "#/components/schemas/Calendar" + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + }, + "user_id": { + "type": "string" + }, + "display_name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true, + "format": "email" + }, + "status": { + "type": "string", + "nullable": true + } + }, + "required": [ + "absent", + "absence_intervals", + "user_id" + ] } - }, - "next_cursor": { - "type": "string", - "nullable": true } }, "required": [ + "from", + "to", + "hits", "results" ] } } } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, - "/v1/calendar/{calendar_id}": { - "get": { - "operationId": "getCalendar", - "summary": "getCalendar", - "description": "Get a single calendar by its epilot ID.", + "/v1/calendar/absence:search": { + "post": { + "operationId": "searchAbsence", + "summary": "searchAbsence", + "description": "Search known absence for candidate users in the requested time window.", "tags": [ - "Calendars" + "Absence" ], - "parameters": [ - { - "in": "path", - "name": "calendar_id", - "schema": { - "type": "string" - }, - "required": true + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchAbsenceBody" + } + } + } + }, + "responses": { + "200": { + "description": "Users absence", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "users": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + } + }, + "required": [ + "absent", + "absence_intervals" + ] + } + } + }, + "required": [ + "from", + "to", + "users" + ] + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } + } + } + }, + "/v1/calendar/absence:search-now": { + "post": { + "operationId": "searchNowAbsence", + "summary": "searchNowAbsence", + "description": "Search known absence for candidate users at the current server time.", + "tags": [ + "Absence" ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchNowAbsenceBody" + } + } + } + }, "responses": { "200": { - "description": "Calendar", + "description": "Users current absence", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Calendar" + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "users": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + }, + "absent_until": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "End of the currently active absence interval, or null when the user is not absent." + } + }, + "required": [ + "absent", + "absence_intervals", + "absent_until" + ] + } + } + }, + "required": [ + "from", + "to", + "users" + ] } } } }, - "404": { - "description": "Resource not found", + "400": { + "description": "Invalid request body", "content": { "application/json": { "schema": { @@ -128,160 +368,1935 @@ } } }, - "/v1/calendar/events": { + "/v1/calendar/absence/users/{user_id}/adjustments": { "get": { - "operationId": "listEvents", - "summary": "listEvents", - "description": "List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own event.", + "operationId": "listAbsenceAdjustments", + "summary": "listAbsenceAdjustments", + "description": "List absence adjustments for a user in a time window.", "tags": [ - "Events" + "Absence" ], "parameters": [ { - "in": "query", - "name": "from", - "description": "Start of the time window (inclusive)", + "in": "path", + "name": "user_id", "schema": { "type": "string", - "format": "date-time", - "description": "Start of the time window (inclusive)" + "minLength": 1 }, "required": true }, { "in": "query", - "name": "to", - "description": "End of the time window (exclusive)", + "name": "from", + "description": "Start of the time window (inclusive). Maximum window: 90 days.", "schema": { "type": "string", "format": "date-time", - "description": "End of the time window (exclusive)" + "description": "Start of the time window (inclusive). Maximum window: 90 days." }, "required": true }, { "in": "query", - "name": "calendar_id", - "description": "Filter to a single calendar. Omit to query across all accessible calendars.", + "name": "to", + "description": "End of the time window (exclusive). Must be after from.", "schema": { "type": "string", - "description": "Filter to a single calendar. Omit to query across all accessible calendars." - } - }, - { - "in": "query", - "name": "size", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 250 - } - }, - { - "in": "query", - "name": "cursor", - "schema": { - "type": "string" - } + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "required": true } ], "responses": { "200": { - "description": "List of events", + "description": "Absence adjustments", "content": { "application/json": { "schema": { "type": "object", "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, "results": { "type": "array", "items": { - "$ref": "#/components/schemas/CalendarEvent" + "$ref": "#/components/schemas/AbsenceAdjustment" } - }, - "next_cursor": { - "type": "string", - "nullable": true } }, "required": [ + "from", + "to", "results" ] } } } }, - "400": { - "description": "Invalid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/v1/calendar/events/{event_id}": { - "get": { - "operationId": "getEvent", - "summary": "getEvent", - "description": "Get a single event by its epilot ID.", - "tags": [ - "Events" - ], - "parameters": [ - { - "in": "path", - "name": "event_id", - "schema": { - "type": "string" + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "createAbsenceAdjustment", + "summary": "createAbsenceAdjustment", + "description": "Create a time-bound absence adjustment for a user in the caller organization.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAbsenceAdjustmentBody" + } + } + } + }, + "responses": { + "201": { + "description": "Created absence adjustment", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AbsenceAdjustment" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}": { + "get": { + "operationId": "getAbsenceAdjustment", + "summary": "getAbsenceAdjustment", + "description": "Get an absence adjustment by ID.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Absence adjustment", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AbsenceAdjustment" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "patchAbsenceAdjustment", + "summary": "patchAbsenceAdjustment", + "description": "Update an absence adjustment in the caller organization.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchAbsenceAdjustmentBody" + } + } + } + }, + "responses": { + "200": { + "description": "Updated absence adjustment", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AbsenceAdjustment" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteAbsenceAdjustment", + "summary": "deleteAbsenceAdjustment", + "description": "Delete an absence adjustment.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Absence adjustment deleted" + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/absence/users/{user_id}": { + "get": { + "operationId": "getUserAbsence", + "summary": "getUserAbsence", + "description": "Get known absence for a user in a time window. absent=false means no known absence, not guaranteed availability.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "query", + "name": "from", + "description": "Start of the time window (inclusive). Maximum window: 31 days.", + "schema": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive). Maximum window: 31 days." + }, + "required": true + }, + { + "in": "query", + "name": "to", + "description": "End of the time window (exclusive). Must be after from.", + "schema": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "required": true + }, + { + "in": "query", + "name": "include_busy", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "schema": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + } + }, + { + "in": "query", + "name": "working_hours_granularity", + "description": "At what granularity working hours contribute to absence. \"time\" (default): all time outside a user's working windows counts as absent. \"day\": only full days with no working windows count as absent (whole-day granularity, used by thread assignment). Users without a working-hours record never produce working-hours intervals.", + "schema": { + "type": "string", + "enum": [ + "time", + "day" + ], + "description": "At what granularity working hours contribute to absence. \"time\" (default): all time outside a user's working windows counts as absent. \"day\": only full days with no working windows count as absent (whole-day granularity, used by thread assignment). Users without a working-hours record never produce working-hours intervals.", + "default": "time" + } + } + ], + "responses": { + "200": { + "description": "User absence", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + }, + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string" + }, + "external_calendars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalCalendar" + }, + "description": "The user's connected external calendars (Outlook/Google), if any." + } + }, + "required": [ + "absent", + "absence_intervals", + "from", + "to", + "user_id" + ] + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/working-hours/users/{user_id}": { + "get": { + "operationId": "getWorkingHours", + "summary": "getWorkingHours", + "description": "Get the recurring weekly working hours of a user. 404 means no record exists and the user is treated as always available.", + "tags": [ + "Working Hours" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Working hours", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingHours" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "operationId": "putWorkingHours", + "summary": "putWorkingHours", + "description": "Create or fully replace the working hours of a user in the caller organization. This is a full replace, not a merge.", + "tags": [ + "Working Hours" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpsertWorkingHoursBody" + } + } + } + }, + "responses": { + "200": { + "description": "Stored working hours", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkingHours" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteWorkingHours", + "summary": "deleteWorkingHours", + "description": "Delete the working hours of a user. The user is then treated as always available again.", + "tags": [ + "Working Hours" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Working hours deleted" + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar": { + "get": { + "operationId": "listCalendars", + "summary": "listCalendars", + "description": "List calendars visible to the caller.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "query", + "name": "size", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 250 + } + }, + { + "in": "query", + "name": "cursor", + "description": "Opaque cursor from a previous response", + "schema": { + "type": "string", + "description": "Opaque cursor from a previous response" + } + } + ], + "responses": { + "200": { + "description": "List of calendars", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Calendar" + } + }, + "next_cursor": { + "type": "string", + "nullable": true + } + }, + "required": [ + "results" + ] + } + } + } + } + } + }, + "post": { + "operationId": "createCalendar", + "summary": "createCalendar", + "description": "Create a native epilot calendar.", + "tags": [ + "Calendars" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarCreateBody" + } + } + } + }, + "responses": { + "201": { + "description": "Calendar created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/sources/outlook": { + "post": { + "operationId": "addOutlookCalendar", + "summary": "addOutlookCalendar", + "description": "Registers one of the caller's Outlook calendars as an epilot calendar.\n\nThe returned calendar resource carries `source.provider=outlook` and the\nprovider calendar id, so subsequent reads through `GET /v1/calendar` /\n`GET /v1/calendar/events` route through the sync provider.", + "tags": [ + "Calendars" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddOutlookCalendarRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Calendar created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Caller has not connected their personal Outlook calendar yet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/sources/outlook/available": { + "get": { + "operationId": "listOutlookCalendars", + "summary": "listOutlookCalendars", + "description": "Lists the calling user's Outlook calendars available to import as epilot calendars.\n\nRequires the caller to have a personal Outlook calendar connection (created via the\nOAuth flow on email-settings: `POST /v2/outlook/connect { \"calendar\": true }`).", + "tags": [ + "Calendars" + ], + "responses": { + "200": { + "description": "List of available Outlook calendars", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "calendars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AvailableOutlookCalendar" + } + } + }, + "required": [ + "calendars" + ] + } + } + } + }, + "404": { + "description": "Caller has not connected their personal Outlook calendar yet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/sources/outlook/{calendar_id}": { + "delete": { + "operationId": "deleteOutlookCalendar", + "summary": "deleteOutlookCalendar", + "description": "Disconnects a previously registered Outlook calendar.\n\nDeletes the Microsoft Graph subscription, removes the backing `calendar`\nentity, and drops the local subscription record. Idempotent: a 404 from\nGraph (subscription already gone) is treated as success.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Outlook calendar disconnected" + }, + "404": { + "description": "No Outlook calendar registration found for this id", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "502": { + "description": "Microsoft Graph subscription deletion failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/outlook/webhook": { + "post": { + "operationId": "outlookWebhook", + "summary": "outlookWebhook", + "description": "Public Microsoft Graph webhook receiver for per-user Outlook calendar\nsubscriptions. Unauthenticated by design (API Gateway `Authorizer: NONE`):\nGraph calls it with no epilot token.\n\nHandles both the subscription-validation handshake (echoes the\n`validationToken` query param as `text/plain`) and change notifications.\nEach notification is trusted only after its HMAC-signed `clientState` is\nverified and matched against the stored subscription – identity is never\nread from the request body.", + "tags": [ + "Calendars" + ], + "security": [], + "parameters": [ + { + "in": "query", + "name": "validationToken", + "description": "Set by Graph on the subscription-validation handshake; echoed back verbatim.", + "schema": { + "type": "string", + "description": "Set by Graph on the subscription-validation handshake; echoed back verbatim." + } + } + ], + "responses": { + "200": { + "description": "Validation handshake echo (text/plain)", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "202": { + "description": "Notification batch accepted" + } + } + } + }, + "/v1/calendar/{calendar_id}": { + "get": { + "operationId": "getCalendar", + "summary": "getCalendar", + "description": "Get a single calendar by its epilot ID.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Calendar", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "updateCalendar", + "summary": "updateCalendar", + "description": "Update local calendar details. Changes to synced calendars do not modify the provider calendar.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarPatchBody" + } + } + } + }, + "responses": { + "200": { + "description": "Calendar updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteCalendar", + "summary": "deleteCalendar", + "description": "Delete a native epilot calendar or disconnect a synced calendar, including its locally stored events.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Calendar deleted or disconnected" + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "The epilot default calendar cannot be deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events": { + "get": { + "operationId": "listEvents", + "summary": "listEvents", + "description": "List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own event.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "query", + "name": "from", + "description": "Start of the time window (inclusive)", + "schema": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive)" + }, + "required": true + }, + { + "in": "query", + "name": "to", + "description": "End of the time window (exclusive)", + "schema": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive)" + }, + "required": true + }, + { + "in": "query", + "name": "calendar_id", + "description": "Filter to a single calendar. Omit to query across all accessible calendars.", + "schema": { + "type": "string", + "description": "Filter to a single calendar. Omit to query across all accessible calendars." + } + }, + { + "in": "query", + "name": "size", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 250 + } + }, + { + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of events", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CalendarEvent" + } + }, + "next_cursor": { + "type": "string", + "nullable": true + } + }, + "required": [ + "results" + ] + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "createEvent", + "summary": "createEvent", + "description": "Create a native epilot calendar event. Omit `calendar_id` to use the caller’s epilot default calendar.", + "tags": [ + "Calendar Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEventCreateBody" + } + } + } + }, + "responses": { + "201": { + "description": "Event created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEvent" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Calendar not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events/{event_id}": { + "get": { + "operationId": "getEvent", + "summary": "getEvent", + "description": "Get a single event by its epilot ID.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Event", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEvent" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "updateEvent", + "summary": "updateEvent", + "description": "Update a native epilot calendar event.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEventPatchBody" + } + } + } + }, + "responses": { + "200": { + "description": "Event updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEvent" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Event or calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteEvent", + "summary": "deleteEvent", + "description": "Delete a native epilot calendar event.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Event deleted" + }, + "403": { + "description": "Event or calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events/{event_id}/share": { + "post": { + "operationId": "shareEvent", + "summary": "shareEvent", + "description": "Share a calendar event with another user of the same organization, view-only. Owner-only: recipients of a share cannot re-share. Sharing an already-shared event is a no-op.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareEventBody" + } + } + } + }, + "responses": { + "204": { + "description": "Event shared" + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Caller is not the event owner", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Event or recipient not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events/{event_id}/share/{user_id}": { + "delete": { + "operationId": "unshareEvent", + "summary": "unshareEvent", + "description": "Revoke a per-event share. The recipient loses access immediately. Owner-only.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Share revoked (or was not present)" + }, + "403": { + "description": "Caller is not the event owner", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Event not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "description": "Authorization header with epilot OAuth2 bearer token", + "bearerFormat": "JWT" + }, + "EpilotOrg": { + "type": "apiKey", + "in": "header", + "name": "x-epilot-org-id" + } + }, + "schemas": { + "AbsenceInterval": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Absence interval start clipped to the requested window." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Absence interval end clipped to the requested window." + }, + "original_from": { + "type": "string", + "format": "date-time", + "description": "Original absence interval start before clipping." + }, + "original_to": { + "type": "string", + "format": "date-time", + "description": "Original absence interval end before clipping." + }, + "absent": { + "type": "boolean", + "description": "Effective absence state for this interval." + }, + "source": { + "$ref": "#/components/schemas/AbsenceIntervalSource" + }, + "calendar_event_id": { + "type": "string", + "description": "calendar_event ID for calendar-derived intervals." + }, + "absence_adjustment_id": { + "type": "string", + "description": "absence adjustment ID for manual intervals." + }, + "reason": { + "type": "string", + "nullable": true + } + }, + "required": [ + "from", + "to", + "original_from", + "original_to", + "absent", + "source" + ] + }, + "AbsenceIntervalSource": { + "type": "string", + "enum": [ + "calendar_event", + "absence_adjustment", + "working_hours" + ] + }, + "Error": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": {} + } + ] + } + }, + "required": [ + "status", + "error" + ] + }, + "SearchAbsenceBody": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive). Maximum window: 31 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "user_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 300, + "description": "Candidate users to check for absence inside the caller organization." + }, + "include_busy": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + }, + "working_hours_granularity": { + "type": "string", + "enum": [ + "time", + "day" + ], + "description": "At what granularity working hours contribute to absence. \"time\" (default): all time outside a user's working windows counts as absent. \"day\": only full days with no working windows count as absent (whole-day granularity, used by thread assignment). Users without a working-hours record never produce working-hours intervals.", + "default": "time" + } + }, + "required": [ + "from", + "to", + "user_ids" + ] + }, + "SearchNowAbsenceBody": { + "type": "object", + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 300, + "description": "Candidate users to check for absence at the current server time inside the caller organization." + }, + "include_busy": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + }, + "working_hours_granularity": { + "type": "string", + "enum": [ + "time", + "day" + ], + "description": "At what granularity working hours contribute to absence. \"time\" (default): all time outside a user's working windows counts as absent. \"day\": only full days with no working windows count as absent (whole-day granularity, used by thread assignment). Users without a working-hours record never produce working-hours intervals.", + "default": "time" + } + }, + "required": [ + "user_ids" + ] + }, + "AbsenceAdjustment": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Adjustment start. Maximum adjustment duration: 7 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Adjustment end. Must be after from." + }, + "absent": { + "type": "boolean", + "description": "Manual override state: true means absent, false means not absent." + }, + "status": { + "$ref": "#/components/schemas/AbsenceStatus" + }, + "type": { + "$ref": "#/components/schemas/AbsenceType" + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 500 + }, + "adjustment_id": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "from", + "to", + "absent", + "status", + "adjustment_id", + "user_id", + "created_by", + "created_at", + "updated_at" + ] + }, + "AbsenceStatus": { + "type": "string", + "enum": [ + "oof", + "busy" + ], + "description": "Calendar status targeted by the absence adjustment." + }, + "AbsenceType": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Optional producer-defined reference stored with an absence adjustment." + }, + "CreateAbsenceAdjustmentBody": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Adjustment start. Maximum adjustment duration: 7 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Adjustment end. Must be after from." + }, + "absent": { + "type": "boolean", + "description": "Manual override state: true means absent, false means not absent." + }, + "status": { + "$ref": "#/components/schemas/AbsenceStatus" + }, + "type": { + "$ref": "#/components/schemas/AbsenceType" + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 500 + } + }, + "required": [ + "from", + "to", + "absent", + "status" + ] + }, + "PatchAbsenceAdjustmentBody": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Adjustment start. Maximum adjustment duration: 7 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Adjustment end. Must be after from." + }, + "absent": { + "type": "boolean", + "description": "Manual override state: true means absent, false means not absent." + }, + "status": { + "$ref": "#/components/schemas/AbsenceStatus" + }, + "type": { + "$ref": "#/components/schemas/AbsenceType" + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 500 + } + } + }, + "ExternalCalendar": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "outlook", + "google" + ], + "description": "External calendar provider." + }, + "last_synced_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Last successful sync of this provider calendar, if any." + } + }, + "required": [ + "provider", + "last_synced_at" + ] + }, + "WorkingHours": { + "type": "object", + "properties": { + "monday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "tuesday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "wednesday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "thursday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "friday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "saturday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "sunday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "timezone": { + "type": "string", + "description": "IANA timezone the working windows are expressed in." + }, + "user_id": { + "type": "string" + }, + "updated_by": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + "timezone", + "user_id", + "updated_by", + "created_at", + "updated_at" + ], + "description": "Recurring weekly working hours of a user. The absence of a record means the user is treated as always available." + }, + "UpsertWorkingHoursBody": { + "type": "object", + "properties": { + "monday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "tuesday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "wednesday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "thursday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "friday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "saturday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" + }, + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "sunday": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimeWindow" }, - "required": true + "maxItems": 6, + "description": "Working windows for a day, sorted and non-overlapping. An empty array means a day off." + }, + "timezone": { + "type": "string", + "description": "IANA timezone the working windows are expressed in. Defaults to Europe/Berlin.", + "default": "Europe/Berlin" } + }, + "required": [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday" ], - "responses": { - "200": { - "description": "Event", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CalendarEvent" - } - } - } + "description": "Full replacement of the working-hours record. All weekdays are required; an empty array means a day off. Users without a working-hours record are treated as always available." + }, + "TimeWindow": { + "type": "object", + "properties": { + "start": { + "type": "string", + "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$", + "description": "24h wall-clock time (\"HH:mm\") in the timezone of the working-hours record." }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "end": { + "type": "string", + "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$", + "description": "24h wall-clock time (\"HH:mm\") in the timezone of the working-hours record." } - } - } - } - }, - "components": { - "securitySchemes": { - "EpilotAuth": { - "type": "http", - "scheme": "bearer", - "description": "Authorization header with epilot OAuth2 bearer token", - "bearerFormat": "JWT" + }, + "required": [ + "start", + "end" + ], + "description": "A wall-clock working window within a single day." }, - "EpilotOrg": { - "type": "apiKey", - "in": "header", - "name": "x-epilot-org-id" - } - }, - "schemas": { "Calendar": { "type": "object", "properties": { @@ -304,7 +2319,8 @@ "description": "Computed display title" }, "name": { - "type": "string" + "type": "string", + "minLength": 1 }, "description": { "type": "string", @@ -317,11 +2333,15 @@ }, "is_default": { "type": "boolean", - "description": "True if this is the user's primary calendar" + "description": "True if the source provider marks this calendar as default" + }, + "is_epilot_default": { + "type": "boolean", + "description": "True for the epilot default calendar" }, "read_only": { "type": "boolean", - "description": "True if the caller cannot modify events in this calendar" + "description": "True if the caller cannot create, update, or delete events in this calendar" }, "owner_email": { "type": "string", @@ -346,6 +2366,7 @@ "_org", "name", "is_default", + "is_epilot_default", "read_only", "source" ] @@ -388,20 +2409,93 @@ ], "description": "Null for native epilot calendars" }, - "Error": { + "CalendarCreateBody": { "type": "object", "properties": { - "message": { - "type": "string" + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "nullable": true + }, + "color": { + "type": "string", + "nullable": true, + "description": "Hex color (e.g. \"#3b82f6\") or provider-defined named color" + } + }, + "required": [ + "name" + ] + }, + "AddOutlookCalendarRequest": { + "type": "object", + "properties": { + "provider_calendar_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Optional override; defaults to the provider calendar name." + } + }, + "required": [ + "provider_calendar_id" + ] + }, + "AvailableOutlookCalendar": { + "type": "object", + "properties": { + "provider_calendar_id": { + "type": "string", + "description": "Microsoft Graph calendar id (immutable, e.g. base64-encoded)" }, - "code": { + "name": { "type": "string" + }, + "color": { + "type": "string", + "nullable": true + }, + "is_default": { + "type": "boolean" + }, + "can_edit": { + "type": "boolean" + }, + "owner": { + "type": "string", + "nullable": true, + "description": "Owner email address on the Microsoft side" } }, "required": [ - "message" + "provider_calendar_id", + "name" ] }, + "CalendarPatchBody": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "nullable": true + }, + "color": { + "type": "string", + "nullable": true, + "description": "Hex color (e.g. \"#3b82f6\") or provider-defined named color" + } + } + }, "CalendarEvent": { "type": "object", "properties": { @@ -432,6 +2526,7 @@ }, "calendar_id": { "type": "string", + "minLength": 1, "description": "epilot calendar this event belongs to" }, "event_type": { @@ -455,6 +2550,7 @@ }, "timezone": { "type": "string", + "minLength": 1, "description": "IANA timezone of the original event (for display)" }, "is_all_day": { @@ -472,7 +2568,12 @@ "description": "Convenience flag, true when status is busy/oof/tentative" }, "is_cancelled": { - "type": "boolean" + "type": "boolean", + "description": "Whether the event was cancelled but still exists" + }, + "is_draft": { + "type": "boolean", + "description": "Whether the event is saved as a draft" }, "sensitivity": { "$ref": "#/components/schemas/Sensitivity" @@ -511,6 +2612,11 @@ }, "description": "Null when sensitivity is private or confidential" }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, "is_recurring": { "type": "boolean" }, @@ -544,6 +2650,7 @@ "status", "busy", "is_cancelled", + "is_draft", "sensitivity", "importance", "is_online_meeting", @@ -669,6 +2776,128 @@ "required": [ "type" ] + }, + "CalendarEventCreateBody": { + "type": "object", + "properties": { + "calendar_id": { + "type": "string", + "minLength": 1, + "description": "Owned calendar ID. Omit this property to use the epilot default calendar." + }, + "description": { + "type": "string", + "nullable": true, + "description": "Preview of the event body, truncated to 255 chars", + "maxLength": 255 + }, + "start_time": { + "type": "string", + "format": "date-time", + "description": "Event start in UTC" + }, + "end_time": { + "type": "string", + "format": "date-time", + "description": "Event end in UTC" + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "IANA timezone of the original event (for display)" + }, + "is_all_day": { + "type": "boolean" + }, + "location": { + "type": "string", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/EventStatus" + }, + "sensitivity": { + "$ref": "#/components/schemas/Sensitivity" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "_title": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "start_time", + "end_time", + "timezone", + "is_all_day", + "status", + "sensitivity", + "_title" + ] + }, + "CalendarEventPatchBody": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "description": "Preview of the event body, truncated to 255 chars", + "maxLength": 255 + }, + "start_time": { + "type": "string", + "format": "date-time", + "description": "Event start in UTC" + }, + "end_time": { + "type": "string", + "format": "date-time", + "description": "Event end in UTC" + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "IANA timezone of the original event (for display)" + }, + "is_all_day": { + "type": "boolean" + }, + "location": { + "type": "string", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/EventStatus" + }, + "is_cancelled": { + "type": "boolean", + "description": "Whether the event was cancelled but still exists" + }, + "sensitivity": { + "$ref": "#/components/schemas/Sensitivity" + }, + "_title": { + "type": "string", + "minLength": 1 + } + } + }, + "ShareEventBody": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "minLength": 1, + "description": "epilot user id (same organization) to grant view-only access to this event" + } + }, + "required": [ + "user_id" + ] } } } diff --git a/packages/cli/definitions/customer-portal.json b/packages/cli/definitions/customer-portal.json index f860f363d..b4b46ae1e 100644 --- a/packages/cli/definitions/customer-portal.json +++ b/packages/cli/definitions/customer-portal.json @@ -480,6 +480,192 @@ } } }, + "/v1/portal/exports": { + "post": { + "operationId": "createExport", + "summary": "createExport", + "description": "Request an asynchronous CSV export of the portal user's entities for one schema. Columns come from the request Returns a job id to poll.\n", + "tags": [ + "ECP" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "schema", + "columns" + ], + "properties": { + "schema": { + "type": "string", + "description": "Entity schema to export (must be an allowed portal entity)." + }, + "search": { + "type": "object", + "description": "Search parameters, same shape the entity list queries with.", + "properties": { + "q": { + "type": "string" + }, + "q_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "filters": { + "type": "array", + "items": { + "type": "object" + } + }, + "filters_context": { + "type": "array", + "items": { + "type": "object" + } + }, + "sort": {} + } + }, + "columns": { + "type": "array", + "description": "Ordered column definitions; order is the CSV column order.", + "items": { + "$ref": "#/components/schemas/PortalDataExportColumn" + } + }, + "expand_over": { + "type": "string" + }, + "language": { + "type": "string", + "enum": [ + "de", + "en" + ] + } + } + } + } + } + }, + "security": [ + { + "PortalAuth": [] + } + ], + "responses": { + "202": { + "description": "Export job accepted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobId": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "ready", + "failed" + ] + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "description": "Missing or invalid export columns." + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/portal/exports/{jobId}": { + "get": { + "operationId": "getExport", + "summary": "getExport", + "description": "Get the status of an export job, including the download URL once ready.", + "tags": [ + "ECP" + ], + "parameters": [ + { + "in": "path", + "name": "jobId", + "required": true, + "schema": { + "type": "string" + }, + "description": "The export job id returned by createExport." + } + ], + "security": [ + { + "PortalAuth": [] + } + ], + "responses": { + "200": { + "description": "Export job status.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobId": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "ready", + "failed", + "expired" + ] + }, + "downloadUrl": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "description": "Export job not found." + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, "/v2/portal/extensions": { "get": { "operationId": "getPortalExtensions", @@ -698,7 +884,7 @@ "/v2/portal/consumption": { "get": { "operationId": "getConsumption", - "summary": "Get Consumption", + "summary": "getConsumption", "description": "Get energy consumption data between a given time period.", "tags": [ "ECP" @@ -775,7 +961,8 @@ "PT15M", "PT1H", "P1D", - "P1M" + "P1M", + "P1Y" ] }, "required": true, @@ -822,6 +1009,17 @@ "type": "string", "description": "Optional unit of the consumption value. Defaults to unit present on the relevant Meter Counter.", "example": "kWh" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Optional localized label for this individual value, keyed by ISO 3166-1 alpha-2 language code (same shape as `VisualizationTypeOption.label`). When present, the portal renders it as the data point label instead of the default timestamp-derived label (e.g. to name billing periods or tariff windows).\n", + "example": { + "en": "Billing period 1", + "de": "Abrechnungszeitraum 1" + } } }, "required": [ @@ -853,7 +1051,7 @@ "/v2/portal/visualization:export": { "post": { "operationId": "prepareVisualizationExport", - "summary": "Prepare Visualization Export", + "summary": "prepareVisualizationExport", "description": "Asks an installed App to prepare a downloadable export of a visualization (consumption chart, dynamic tariff chart, etc.). The export is produced by the third-party App via a configured portal extension hook of type `dataExport` — this endpoint does not generate the file itself, it forwards the request to the configured hook and returns the descriptor the App provides (typically a `download_url`).\n", "tags": [ "ECP" @@ -957,7 +1155,7 @@ "/v2/portal/visualization/metadata": { "get": { "operationId": "getVisualizationMetadata", - "summary": "Get Visualization Metadata", + "summary": "getVisualizationMetadata", "description": "Returns runtime metadata describing how a visualization (consumption / price / cost chart) should be rendered for a given portal context (meter, contract, etc). Resolves the extension's `visualizationMetadata` hook implicitly from `app_id` + `extensionId` and invokes it. Supplies the response as a structured payload that the portal uses to configure type/aggregation options, supported intervals, and the available data range.\n", "tags": [ "ECP" @@ -1025,7 +1223,7 @@ "/v2/portal/costs": { "get": { "operationId": "getCosts", - "summary": "Get Costs", + "summary": "getCosts", "description": "Get energy cost data between a given time period.", "tags": [ "ECP" @@ -1102,7 +1300,8 @@ "PT15M", "PT1H", "P1D", - "P1M" + "P1M", + "P1Y" ] }, "required": true, @@ -1199,7 +1398,7 @@ "/v2/portal/prices": { "get": { "operationId": "getPrices", - "summary": "Get Prices", + "summary": "getPrices", "description": "Get energy prices data between a given time period.", "tags": [ "ECP" @@ -1276,7 +1475,8 @@ "PT15M", "PT1H", "P1D", - "P1M" + "P1M", + "P1Y" ] }, "required": true, @@ -2190,11 +2390,11 @@ } } }, - "/v3/portal/email-templates/{portal_id}": { + "/v3/portal/email-templates:migrate-references": { "post": { - "operationId": "upsertEmailTemplatesByPortalId", - "summary": "upsertEmailTemplatesByPortalId", - "description": "Upserts the email templates of a portal by portal ID", + "operationId": "migrateEmailTemplateReferences", + "summary": "migrateEmailTemplateReferences", + "description": "Walk every email-template config row in the caller's org and re-point any\nfield on `email_templates` that currently references `source_template_id`\nat `destination_template_id`. Intended to be called from the email-template\nmigration flow when a duplicated template is refined.\n\nOnly v3-shaped rows are migrated (those carrying `portal_sk_v3`). Returns\nthe portal IDs that were rewritten and any whose update failed.\n", "tags": [ "ECP Admin" ], @@ -2203,48 +2403,59 @@ "EpilotAuth": [] } ], - "parameters": [ - { - "in": "path", - "name": "portal_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "ID of the portal" - } - ], "requestBody": { - "description": "Email templates payload", + "description": "Source and destination template ids", "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmailTemplates" + "type": "object", + "required": [ + "source_template_id", + "destination_template_id" + ], + "properties": { + "source_template_id": { + "type": "string", + "description": "Template id currently referenced on portal rows" + }, + "destination_template_id": { + "type": "string", + "description": "Template id to write in place of the source" + } + } } } } }, "responses": { "200": { - "description": "Upserted email templates of the portal successfully.", + "description": "Migration completed (may have partial failures in failed_portal_ids).", "content": { "application/json": { "schema": { "type": "object", "required": [ - "message", - "emailTemplates" + "migrated_portal_count", + "migrated_portal_ids", + "failed_portal_ids" ], "properties": { - "message": { - "type": "string", - "enum": [ - "Email Templates upserted successfully" - ] + "migrated_portal_count": { + "type": "integer", + "example": 2 }, - "emailTemplates": { - "$ref": "#/components/schemas/EmailTemplates" + "migrated_portal_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "failed_portal_ids": { + "type": "array", + "items": { + "type": "string" + } } } } @@ -2261,11 +2472,13 @@ "$ref": "#/components/responses/InternalServerError" } } - }, - "get": { - "operationId": "getEmailTemplatesByPortalId", - "summary": "getEmailTemplatesByPortalId", - "description": "Retrieves the email templates of a portal by portal ID", + } + }, + "/v3/portal/email-templates:list-references": { + "post": { + "operationId": "listEmailTemplateReferences", + "summary": "listEmailTemplateReferences", + "description": "Read-only sibling of migrateEmailTemplateReferences. Lists every portal in\nthe caller's org whose `email_templates` config references `template_id`,\nwithout rewriting anything. Used by the email-template MFE to show which\nportals a template affects (in template settings and as a pre-migrate\npreview). Uses the same discovery as the migrate path.\n", "tags": [ "ECP Admin" ], @@ -2274,24 +2487,57 @@ "EpilotAuth": [] } ], - "parameters": [ - { - "in": "path", - "name": "portal_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "ID of the portal" + "requestBody": { + "description": "Template id to look up references for", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "template_id" + ], + "properties": { + "template_id": { + "type": "string", + "description": "Email template id to find portal references for" + } + } + } + } } - ], + }, "responses": { "200": { - "description": "Retrieved the email templates successfully.", + "description": "Portals referencing the template.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmailTemplates" + "type": "object", + "required": [ + "portals" + ], + "properties": { + "portals": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Portal display name (or domain); falls back to id when unavailable" + } + } + } + } + } } } } @@ -2308,12 +2554,130 @@ } } }, - "/v2/portal/public-widgets": { - "get": { - "operationId": "getPublicPortalWidgets", - "summary": "getPublicPortalWidgets", - "description": "Retrieves the public widgets of a portal.", - "deprecated": true, + "/v3/portal/email-templates/{portal_id}": { + "post": { + "operationId": "upsertEmailTemplatesByPortalId", + "summary": "upsertEmailTemplatesByPortalId", + "description": "Upserts the email templates of a portal by portal ID", + "tags": [ + "ECP Admin" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "portal_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID of the portal" + } + ], + "requestBody": { + "description": "Email templates payload", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailTemplates" + } + } + } + }, + "responses": { + "200": { + "description": "Upserted email templates of the portal successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "emailTemplates" + ], + "properties": { + "message": { + "type": "string", + "enum": [ + "Email Templates upserted successfully" + ] + }, + "emailTemplates": { + "$ref": "#/components/schemas/EmailTemplates" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "get": { + "operationId": "getEmailTemplatesByPortalId", + "summary": "getEmailTemplatesByPortalId", + "description": "Retrieves the email templates of a portal by portal ID", + "tags": [ + "ECP Admin" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "portal_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID of the portal" + } + ], + "responses": { + "200": { + "description": "Retrieved the email templates successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailTemplates" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/public-widgets": { + "get": { + "operationId": "getPublicPortalWidgets", + "summary": "getPublicPortalWidgets", + "description": "Retrieves the public widgets of a portal.", + "deprecated": true, "tags": [ "Public" ], @@ -2765,6 +3129,14 @@ }, "is_main_entity": { "type": "boolean" + }, + "journey_registration_email": { + "type": "string", + "description": "The email address registered in the journey's Login & Registration block" + }, + "journey_registration_portal_id": { + "type": "string", + "description": "The portal selected on the journey's Login & Registration block, used to target the registration link to that portal" } } } @@ -3501,6 +3873,14 @@ "contactId": { "$ref": "#/components/schemas/EntityId", "description": "ID of the contact if exists" + }, + "reason": { + "type": "string", + "enum": [ + "TIMEOUT", + "NOT_FOUND" + ], + "description": "Present only when exists is false. NOT_FOUND means the given identifiers did\nnot match any contact (definitive - the client should not retry). TIMEOUT\nmeans the contact was not found within the processing window but may still be\ningesting; the client may retry (ideally with trigger_identifiers_check=false).\n" } } } @@ -3579,6 +3959,14 @@ "accountId": { "$ref": "#/components/schemas/EntityId", "description": "ID of the resolved account when the portal is configured for account-based registration" + }, + "reason": { + "type": "string", + "enum": [ + "TIMEOUT", + "NOT_FOUND" + ], + "description": "Present only when exists is false. NOT_FOUND means the given identifiers did\nnot match any contact (definitive - the client should not retry). TIMEOUT\nmeans the contact was not found within the processing window but may still be\ningesting; the client may retry (ideally with trigger_identifiers_check=false).\n" } } } @@ -3831,9 +4219,8 @@ "properties": { "message": { "type": "string", - "enum": [ - "User Succesfully Deleted" - ] + "description": "`User Succesfully Deleted` when the user was deleted, or `Account deletion requested`\nwhen an asynchronous deleteAccount portal extension hook handed the deletion over to a third party.\n", + "example": "User Succesfully Deleted" }, "data": { "$ref": "#/components/schemas/EntityId" @@ -3873,8 +4260,7 @@ "schema": { "type": "object", "required": [ - "email", - "password" + "email" ], "properties": { "email": { @@ -3884,7 +4270,7 @@ }, "password": { "type": "string", - "description": "Password of the portal user for confirmation" + "description": "Password of the portal user for confirmation.\nRequired unless a `changeEmail` portal extension hook with `require_password_confirmation` disabled is configured for the portal,\nor the portal user's identity is managed by an identity provider - an SSO login has no password to confirm.\n" } } } @@ -3893,7 +4279,7 @@ }, "responses": { "200": { - "description": "Portal user email updated successfully and will receive a confirmation email soon.", + "description": "Portal user email updated successfully.\nThe user receives a confirmation email, unless a `changeEmail` portal extension hook with\n`change_mode` set to `synchronous` changed the login email right away.\n", "content": { "application/json": { "schema": { @@ -3901,9 +4287,66 @@ "properties": { "message": { "type": "string", - "enum": [ - "You will receive a confirmation mail soon on your updated email address." - ] + "description": "`You will receive a confirmation mail soon on your updated email address.` for the built-in flow,\n`Your email change request has been received.` when a changeEmail portal extension hook handed\nthe change over to a third party, or `Your email has been changed.` when a synchronous changeEmail\nportal extension hook changed the login email immediately - the portal user has to sign in again\nwith the new email address in that case.\n", + "example": "You will receive a confirmation mail soon on your updated email address." + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/user/change/password": { + "put": { + "operationId": "changePortalUserPassword", + "summary": "changePortalUserPassword", + "description": "Hand over a password change to the third-party system configured via the `changePassword` portal extension hook.\nOnly available when such a hook is configured for the portal; the built-in password change flow does not use this endpoint.\n", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "requestBody": { + "description": "Request payload", + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_password": { + "type": "string", + "description": "New password chosen by the portal user.\nRequired when the configured `changePassword` hook has `require_new_password` enabled, ignored otherwise.\n" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The password change request was handed over to the third-party system.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" } } } @@ -3916,6 +4359,9 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "404": { + "description": "No changePassword portal extension hook is configured for this portal." + }, "500": { "$ref": "#/components/responses/InternalServerError" } @@ -5567,11 +6013,11 @@ } } }, - "/v2/portal/contract/by-identifiers": { + "/v2/portal/contract/{id}/resolve-templates": { "post": { - "operationId": "addContractByIdentifiers", - "summary": "addContractByIdentifiers", - "description": "Self-assign contract(s) by pre-configured identifiers.", + "operationId": "getContractWithTemplates", + "summary": "getContractWithTemplates", + "description": "Resolve Handlebars templates against a contract's related meters and return the contract with templates_output populated per meter. This is a read-only operation — it does not modify the contract. It uses POST only so the templates can be passed in the request body. Prefer getPortalUserEntity / getContract for reads that don't need templates.", "tags": [ "ECP" ], @@ -5580,26 +6026,35 @@ "PortalAuth": [] } ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "$ref": "#/components/schemas/EntityId" + }, + "required": true, + "description": "The ID of the contract" + } + ], "requestBody": { - "description": "Identifiers to search by. Have to be pre-configured in Portal Configuration Permissions.", - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "description": "Identifier-value pairs per schema to identify the contract", - "additionalProperties": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "example": { - "contract": { - "contract_number": "123456" + "properties": { + "templates": { + "type": "object", + "deprecated": true, + "additionalProperties": { + "type": "string" + }, + "description": "DEPRECATED — client-supplied map of content field name to Handlebars template string, resolved per related meter and returned as templates_output on the meter. Use `templates_ref` instead; ignored when `templates_ref` is present and rejected once the org has the `portals-reject-client-templates` flag enabled." }, - "meter": { - "meter_number": "123456" + "templates_ref": { + "$ref": "#/components/schemas/TemplatesRef", + "description": "Reference to the meter selector block whose admin-configured content provides the templates, derived server-side." } } } @@ -5608,39 +6063,156 @@ }, "responses": { "200": { - "description": "Contract(s) assigned successfully.", + "description": "The requested contract returned successfully.", "content": { "application/json": { "schema": { "type": "object", "properties": { - "data": { + "entity": { + "$ref": "#/components/schemas/Contract" + }, + "orders": { "type": "array", + "description": "The related orders of the requested contract", "items": { - "$ref": "#/components/schemas/EntityItem" + "$ref": "#/components/schemas/Order" } }, - "hits": { - "type": "number", - "minimum": 0 - } - }, - "required": [ - "hits" - ] - } - } - } - }, - "400": { - "$ref": "#/components/responses/InvalidRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, + "meters": { + "type": "array", + "description": "The related meters of the requested contract", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/Meter" + }, + { + "$ref": "#/components/schemas/EntityTemplates" + } + ] + } + }, + "files": { + "type": "array", + "description": "The related files of the requested contract", + "items": { + "$ref": "#/components/schemas/File" + } + }, + "relations": { + "type": "array", + "description": "The related entities of the requested contract", + "items": { + "$ref": "#/components/schemas/EntityItem" + } + }, + "workflow": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowExecution" + } + }, + "journey_actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JourneyActions" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/contract/by-identifiers": { + "post": { + "operationId": "addContractByIdentifiers", + "summary": "addContractByIdentifiers", + "description": "Self-assign contract(s) by pre-configured identifiers.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "requestBody": { + "description": "Identifiers to search by. Have to be pre-configured in Portal Configuration Permissions.", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Identifier-value pairs per schema to identify the contract", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "example": { + "contract": { + "contract_number": "123456" + }, + "meter": { + "meter_number": "123456" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Contract(s) assigned successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityItem" + } + }, + "hits": { + "type": "number", + "minimum": 0 + } + }, + "required": [ + "hits" + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, "409": { "$ref": "#/components/responses/ContractAssignmentConflict" }, @@ -6416,6 +6988,56 @@ } } }, + "/v2/portal/user/file/{id}/preview": { + "get": { + "operationId": "getFilePreview", + "summary": "getFilePreview", + "description": "resolves an in-portal preview for a file. Returns a Content-Disposition: inline URL for directly-previewable files (PDF, common image formats), or a document-api PDF conversion result for the Office formats it currently supports (docx/xls/xlsx/xlsm) -- NOT doc/csv/pptx, which document-api's conversion does not cover today. Returns { kind: \"unsupported\" } (no url) for anything else, so the caller can fall back to the existing download flow. An optional `download_url` is returned for all kinds — including \"unsupported\" — whenever the file entity exposes one, so the client can render a real anchor for its Download action; it is only a Content-Disposition: attachment URL for private files, and for other access levels it is the plain object URL, which may render inline. The previewability decision is made server-side from the file's own filename/mime_type — never trust a client-supplied kind for anything security-sensitive.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "$ref": "#/components/schemas/EntityId" + }, + "required": true, + "description": "The Id of a file" + } + ], + "responses": { + "200": { + "description": "The file preview was resolved (which may still indicate kind \"unsupported\" with no url).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilePreviewResult" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, "/v2/portal/user/file/{id}/downloaded": { "post": { "operationId": "trackFileDownloaded", @@ -6474,7 +7096,7 @@ "/v2/portal/user/files/count-by-entity": { "get": { "operationId": "getFilesCountByEntity", - "summary": "getFileCountByEntity", + "summary": "getFilesCountByEntity", "description": "Fetch file counts for all ECP user related entities", "deprecated": true, "tags": [ @@ -6610,8 +7232,8 @@ "in": "query", "schema": { "type": "string", - "description": "Key to sort by", - "example": "due_date:asc" + "description": "Key to sort by. Pass a comma-separated list to apply additional keys as tiebreakers, in order of precedence.", + "example": "paid_date:desc,booking_date:desc" } } ], @@ -7034,102 +7656,252 @@ } } }, - "/v2/portal/can-trigger-portal-flow": { + "/v2/portal/entity/{slug}": { "post": { - "operationId": "canTriggerPortalFlow", - "summary": "canTriggerPortalFlow", - "description": "Returns whether the user can trigger a portal flow", + "operationId": "createPortalUserEntity", + "summary": "createPortalUserEntity", + "description": "**EXPERIMENTAL — do not rely on this endpoint.** It is unstable, currently limited to the `asset` schema, and may change or be removed without notice; third parties must not build on it yet.\nCreate a single entity on behalf of a portal user. The schema slug is passed in the path and must be one of the supported (experimental) schemas; field-level permissions are enforced by the caller's role grants. The request body is the entity to create (its attributes); the created entity is automatically related to the caller's contact.", + "deprecated": true, "tags": [ - "ECP Admin" + "ECP" ], "security": [ { - "EpilotAuth": [] + "PortalAuth": [] } ], "parameters": [ { - "in": "query", - "name": "origin", - "required": false, - "deprecated": true, - "schema": { - "$ref": "#/components/schemas/Origin" - }, - "description": "Origin of the portal" - }, - { - "in": "query", - "name": "portal_id", + "in": "path", + "name": "slug", + "description": "Entity schema slug to create. Limited to the supported (experimental) schemas, currently `asset`.", "required": true, "schema": { "type": "string", - "description": "Portal ID", - "example": "123" + "enum": [ + "asset" + ], + "example": "asset" } } ], "requestBody": { - "description": "Request of trigger portal flow", "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TriggerPortalFlow" + "$ref": "#/components/schemas/Entity" + }, + "example": { + "title": "PV Inverter", + "manufacturer": "SMA", + "external_id": "device-123" } } } }, "responses": { - "200": { - "description": "Can Trigger Portal Flow", + "201": { + "description": "The entity has been created successfully for the portal user.", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "can_trigger": { - "type": "boolean", - "description": "Whether the flow can be triggered", - "example": true - } - } + "$ref": "#/components/schemas/EntityResponse" } } } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } } }, - "/v2/portal/automation-context": { - "get": { - "operationId": "getAutomationContext", - "summary": "getAutomationContext", - "description": "Retrieves the automation context.", + "/v2/portal/entity/{slug}/{id}": { + "patch": { + "operationId": "patchPortalUserEntity", + "summary": "patchPortalUserEntity", + "description": "**EXPERIMENTAL — do not rely on this endpoint.** It is unstable, currently limited to the `asset` schema, and may change or be removed without notice; third parties must not build on it yet.\nPartially update a single entity on behalf of a portal user. The schema slug and entity id are passed in the path; the schema must be one of the supported (experimental) schemas. Field-level permissions are enforced by the caller's role grants (use null to clear a field, e.g. external_id). The target entity must already be owned by the caller's contact.", + "deprecated": true, "tags": [ "ECP" ], "security": [ { - "EpilotAuth": [] + "PortalAuth": [] } ], "parameters": [ { - "in": "query", - "name": "activity_id", + "in": "path", + "name": "slug", + "description": "Entity schema slug to update. Limited to the supported (experimental) schemas, currently `asset`.", "required": true, "schema": { - "$ref": "#/components/schemas/ActivityId" - }, - "description": "Activity ID" + "type": "string", + "enum": [ + "asset" + ], + "example": "asset" + } }, { - "in": "query", - "name": "type", + "in": "path", + "name": "id", + "description": "ID of the entity to update. Must already be owned by the caller's contact.", "required": true, "schema": { - "type": "string", + "$ref": "#/components/schemas/EntityId" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entity" + }, + "example": { + "external_id": null + } + } + } + }, + "responses": { + "200": { + "description": "The entity has been updated successfully for the portal user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/can-trigger-portal-flow": { + "post": { + "operationId": "canTriggerPortalFlow", + "summary": "canTriggerPortalFlow", + "description": "Returns whether the user can trigger a portal flow", + "tags": [ + "ECP Admin" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "origin", + "required": false, + "deprecated": true, + "schema": { + "$ref": "#/components/schemas/Origin" + }, + "description": "Origin of the portal" + }, + { + "in": "query", + "name": "portal_id", + "required": true, + "schema": { + "type": "string", + "description": "Portal ID", + "example": "123" + } + } + ], + "requestBody": { + "description": "Request of trigger portal flow", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TriggerPortalFlow" + } + } + } + }, + "responses": { + "200": { + "description": "Can Trigger Portal Flow", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "can_trigger": { + "type": "boolean", + "description": "Whether the flow can be triggered", + "example": true + } + } + } + } + } + } + } + } + }, + "/v2/portal/automation-context": { + "get": { + "operationId": "getAutomationContext", + "summary": "getAutomationContext", + "description": "Retrieves the automation context.", + "tags": [ + "ECP" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "activity_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/ActivityId" + }, + "description": "Activity ID" + }, + { + "in": "query", + "name": "type", + "required": true, + "schema": { + "type": "string", "enum": [ "file" ] @@ -7212,7 +7984,7 @@ "/v2/portal/entity/{slug}/{id}/workflows": { "get": { "operationId": "getEntityWorkflows", - "summary": "Get workflows for an entity", + "summary": "getEntityWorkflows", "description": "Get all workflows associated with an entity (requires access to the entity)", "tags": [ "ECP" @@ -7278,11 +8050,11 @@ } } }, - "/v2/portal/metering/reading/photo": { - "post": { - "operationId": "uploadMeterReadingPhoto", - "summary": "Upload Meter Reading Photo", - "description": "Uploads a Meter Reading photo and - if enabled - gives back data extracted from the photo.", + "/v2/portal/engagement/tasks": { + "get": { + "operationId": "getOutstandingTasks", + "summary": "Get outstanding workflow tasks for the portal user", + "description": "Returns all outstanding workflow journey tasks for the authenticated portal user, across their opportunity, order and contract entities. Each task is an active workflow step that exposes a journey the customer still needs to fill out.", "tags": [ "ECP" ], @@ -7291,36 +8063,33 @@ "PortalAuth": [] } ], - "requestBody": { - "description": "Upload Meter reading photo payload.", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MeterReadingPhoto" - } - } - } - }, "responses": { "200": { - "description": "Meter reading photo saved successfully.", + "description": "List of outstanding workflow tasks for the portal user", "content": { "application/json": { "schema": { "type": "object", + "required": [ + "tasks", + "total" + ], "properties": { - "data": { - "$ref": "#/components/schemas/MeterReadingPhotoData" + "tasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutstandingTask" + } + }, + "total": { + "type": "integer", + "example": 0 } } } } } }, - "400": { - "$ref": "#/components/responses/InvalidRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, @@ -7333,11 +8102,11 @@ } } }, - "/v2/portal/metering/reading": { - "post": { - "operationId": "createMeterReading", - "summary": "Create Meter Reading", - "description": "Inserts a new meter reading.", + "/v2/portal/entity/{slug}/{id}/workflows/linearized": { + "get": { + "operationId": "getEntityPortalWorkflows", + "summary": "getEntityPortalWorkflows", + "description": "Get all portal-relevant workflows associated with an entity (requires access to the entity),\nlinearized by the Workflows API into a flat, ordered list of portal-visible tasks.\n\nUnlike `getEntityWorkflows`, this endpoint returns a single uniform shape for both legacy\n(V1) workflow executions and graph-based (V2) flow executions — the execution graph is\nalready resolved server-side (active path, task reachability, chronological ordering), so\nclients can render a timeline without any graph traversal logic.\n\nTasks carry the config of both portal audiences (`ecp` and `installer`); clients filter\nper their configured audience, so one response serves every block on a page.\n", "tags": [ "ECP" ], @@ -7348,62 +8117,65 @@ ], "parameters": [ { - "name": "override_plausibility", - "in": "query", - "required": false, - "description": "Override plausibility check", + "in": "path", + "name": "slug", + "required": true, "schema": { - "type": "boolean", - "default": false + "type": "string", + "description": "Schema slug of the entity", + "example": "opportunity" } - } - ], - "requestBody": { - "description": "Meter reading payload.", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MeterReading" - } + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string", + "description": "ID of the entity", + "example": "abc123" } } - }, + ], "responses": { "200": { - "description": "Meter reading created successfully.", + "description": "Portal-relevant workflows for the entity, each with linearized tasks", "content": { "application/json": { "schema": { "type": "object", "properties": { - "data": { - "$ref": "#/components/schemas/MeterReading" + "portal_workflows": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PortalWorkflow" + } } } } } } }, - "400": { - "$ref": "#/components/responses/InvalidRequestCreateMeterReading" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, + "404": { + "$ref": "#/components/responses/NotFound" + }, "500": { "$ref": "#/components/responses/InternalServerError" } } } }, - "/v2/portal/metering/reading/allowed-range/{meter_id}": { - "get": { - "operationId": "getAllowedMeterReadingRange", - "description": "Get allowed reading range for all counters of a meter from the configured\nthird-party plausibility check hook using 'range' mode. This endpoint requires\na plausibility check hook to be configured for the portal.\n", + "/v2/portal/entities/workflows/linearized/batch": { + "post": { + "operationId": "getEntityPortalWorkflowsBatch", + "summary": "getEntityPortalWorkflowsBatch", + "description": "Batch variant of `getEntityPortalWorkflows`: returns portal-relevant workflows for\nup to 50 entities — schemas may differ — in a single call, each already linearized\nby the Workflows API into a flat, ordered list of portal-visible tasks.\n\nIntended for portal list views (e.g. an entity list where every card shows its\nworkflow progress). Entities the portal user cannot access are omitted from the\nresponse; an entry with an empty task list means the entity has no portal-relevant\nworkflow.\n", "tags": [ "ECP" ], @@ -7412,37 +8184,245 @@ "PortalAuth": [] } ], - "parameters": [ - { - "in": "path", - "name": "meter_id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The ID of the meter." - }, - { - "in": "query", - "name": "origin", - "required": false, - "schema": { - "$ref": "#/components/schemas/Origin" - }, - "description": "Origin of the portal" - }, - { - "in": "query", - "name": "timestamp", - "required": false, - "description": "If not provided, the system will default to now.", - "schema": { - "type": "string", - "example": "2022-10-01T10:10:00.000Z" - } - }, - { - "in": "query", + "requestBody": { + "description": "Entities to load portal workflows for", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the entity" + }, + "slug": { + "type": "string", + "description": "Schema slug of the entity", + "example": "order" + } + }, + "required": [ + "id", + "slug" + ] + }, + "minItems": 1, + "maxItems": 50 + } + }, + "required": [ + "entities" + ] + } + } + } + }, + "responses": { + "200": { + "description": "One entry per accessible entity, each with its linearized workflows", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityPortalWorkflows" + } + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/metering/reading/photo": { + "post": { + "operationId": "uploadMeterReadingPhoto", + "summary": "uploadMeterReadingPhoto", + "description": "Uploads a Meter Reading photo and - if enabled - gives back data extracted from the photo.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "requestBody": { + "description": "Upload Meter reading photo payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeterReadingPhoto" + } + } + } + }, + "responses": { + "200": { + "description": "Meter reading photo saved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/MeterReadingPhotoData" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/metering/reading": { + "post": { + "operationId": "createMeterReading", + "summary": "createMeterReading", + "description": "Inserts a new meter reading.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "parameters": [ + { + "name": "override_plausibility", + "in": "query", + "required": false, + "description": "Override plausibility check", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "description": "Meter reading payload.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeterReading" + } + } + } + }, + "responses": { + "200": { + "description": "Meter reading created successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/MeterReading" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequestCreateMeterReading" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/metering/reading/allowed-range/{meter_id}": { + "get": { + "operationId": "getAllowedMeterReadingRange", + "summary": "getAllowedMeterReadingRange", + "description": "Get allowed reading range for all counters of a meter from the configured\nthird-party plausibility check hook using 'range' mode. This endpoint requires\na plausibility check hook to be configured for the portal.\n", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "meter_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The ID of the meter." + }, + { + "in": "query", + "name": "origin", + "required": false, + "schema": { + "$ref": "#/components/schemas/Origin" + }, + "description": "Origin of the portal" + }, + { + "in": "query", + "name": "timestamp", + "required": false, + "description": "If not provided, the system will default to now.", + "schema": { + "type": "string", + "example": "2022-10-01T10:10:00.000Z" + } + }, + { + "in": "query", "name": "context_entities", "required": false, "description": "Additional entities to include in the context for variable interpolation in the hook.", @@ -7504,7 +8484,7 @@ "/v2/portal/metering/readings": { "post": { "operationId": "getMeterReadings", - "summary": "Get meter readings with optional template resolution", + "summary": "getMeterReadings", "description": "Fetches meter readings for a counter and optionally resolves Handlebars\ntemplate strings against each reading object using @epilot/variables.\n", "tags": [ "ECP" @@ -7549,17 +8529,23 @@ }, "templates": { "type": "object", + "deprecated": true, "additionalProperties": { "type": "string" }, - "description": "Template map (key to Handlebars template string). Each template is resolved per reading." + "description": "DEPRECATED — client-supplied template map (key to Handlebars template string), resolved per reading. Use `templates_ref` instead; ignored when `templates_ref` is present and rejected once the org has the `portals-reject-client-templates` flag enabled." }, "counter_templates": { "type": "object", + "deprecated": true, "additionalProperties": { "type": "string" }, - "description": "Template map resolved against the counter entity." + "description": "DEPRECATED — client-supplied template map resolved against the counter entity. Use `templates_ref` instead; same deprecation rules as `templates`." + }, + "templates_ref": { + "$ref": "#/components/schemas/TemplatesRef", + "description": "Reference to the meter reading block whose admin-configured content provides both the per-reading templates (content_top_name, main_content_name, content_bottom_name) and the counter templates (counter_title_name, counter_subtitle_name), derived server-side." } } } @@ -8331,7 +9317,7 @@ "post": { "operationId": "interpolatePortalPages", "summary": "interpolatePortalPages", - "description": "Interpolate template variables in portal pages without reading from the database. Accepts pages in the request body and returns them with templates resolved.", + "description": "Interpolate template variables in portal pages without reading from the database. Accepts pages in the request body and returns them with templates resolved. Portal Builder preview only: requires a 360 (epilot) token or a `portal_preview` token; plain portal user tokens get 403.", "tags": [ "ECP Admin" ], @@ -8849,7 +9835,7 @@ "/v2/portal/campaign/{campaign_id}/entity:status": { "put": { "operationId": "updateCampaignPortalBlockStatus", - "summary": "Update Campaign Portal Block Status", + "summary": "updateCampaignPortalBlockStatus", "description": "Updates the status of a campaign portal block for multiple recipients.", "tags": [ "ECP" @@ -8972,11 +9958,11 @@ } } }, - "/v2/portal/notifications/entity:status": { - "put": { - "operationId": "updateNotificationsStatus", - "summary": "updateNotificationsStatus", - "description": "Updates the statuses of multiple notifications at once.", + "/v2/portal/notifications": { + "get": { + "operationId": "listPortalNotifications", + "summary": "listPortalNotifications", + "description": "Lists the 360 notifications addressed to the authenticated portal user, newest first. The organization and the portal user are derived from the authenticated session, so a user can only ever read their own notifications.", "tags": [ "ECP" ], @@ -8985,30 +9971,216 @@ "PortalAuth": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "notifications" - ], - "properties": { - "notifications": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "status" - ], - "properties": { - "id": { - "type": "string", - "description": "ID of the notification" - }, - "status": { + "parameters": [ + { + "in": "query", + "name": "cursor", + "required": false, + "schema": { + "type": "string" + }, + "description": "Base64 encoded cursor returned by a previous call, used for pagination." + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + }, + "description": "The maximum number of notifications to return." + } + ], + "responses": { + "200": { + "description": "List of notifications for the authenticated portal user.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cursor": { + "type": "string", + "description": "Base64 encoded cursor to fetch the next page. Absent when there are no more results." + }, + "total_unread": { + "type": "integer", + "description": "Total number of unread notifications for the user." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PortalNotification" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/notifications/unread-count": { + "get": { + "operationId": "getPortalNotificationsUnreadCount", + "summary": "getPortalNotificationsUnreadCount", + "description": "Returns the number of unread notifications for the authenticated portal user.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "responses": { + "200": { + "description": "Unread notification count.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "example": 3 + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/notifications/read-all": { + "put": { + "operationId": "markAllPortalNotificationsRead", + "summary": "markAllPortalNotificationsRead", + "description": "Marks all notifications of the authenticated portal user as read.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "responses": { + "204": { + "description": "All notifications marked as read." + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/notifications/{id}/read": { + "put": { + "operationId": "markPortalNotificationRead", + "summary": "markPortalNotificationRead", + "description": "Marks a single notification of the authenticated portal user as read.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + }, + "description": "Numeric id of the notification to mark as read." + } + ], + "responses": { + "204": { + "description": "Notification marked as read." + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/portal/notifications/entity:status": { + "put": { + "operationId": "updateNotificationsStatus", + "summary": "updateNotificationsStatus", + "description": "Updates the statuses of multiple notifications at once.", + "tags": [ + "ECP" + ], + "security": [ + { + "PortalAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "notifications" + ], + "properties": { + "notifications": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "status" + ], + "properties": { + "id": { + "type": "string", + "description": "ID of the notification" + }, + "status": { "type": "string", "enum": [ "read", @@ -9319,6 +10491,20 @@ "example": "5da0a718-c822-403d-9f5d-20d4584e0528" }, "description": "Portal ID (readonly UUID generated on portal creation)" + }, + { + "in": "query", + "name": "page_upsert_mode", + "required": false, + "schema": { + "type": "string", + "enum": [ + "id", + "slug" + ], + "default": "id" + }, + "description": "Determines how pages are matched for upsert operations:\n- `id` (default): Match pages by their ID. Use this when page IDs are stable and known upfront.\n- `slug`: Match pages by their slug. When a request page has the same slug as an existing page, the existing page ID is adopted. Use this when page ids are unknown or when source page IDs differ from destination page IDs.\n" } ], "requestBody": { @@ -9489,7 +10675,7 @@ "items": { "$ref": "#/components/schemas/SwappableConfig" }, - "description": "Items to swap" + "description": "Optional, opt-in configuration items to additionally swap on top of the always-swapped pages and functional config. Defaults to an empty list (nothing extra swapped). Domain and access/security settings can never be swapped." } } } @@ -9624,13 +10810,6 @@ "type": "string", "description": "Email address of the partner to invite" }, - "represents_contact_list": { - "type": "array", - "items": { - "$ref": "#/components/schemas/EntityId", - "description": "List of contact IDs representing the contacts / business partners the invited user should be added to.\nWhen omitted, the user is automatically added to all contacts / business partners linked to the account.\n" - } - }, "contact_data": { "type": "object", "description": "Additional contact entity fields to set when creating the contact for the invited user.\nThese are mapped directly to contact entity attributes (e.g. first_name, last_name, phone).\nValues can be strings or arrays of strings (for multiselect attributes).\n", @@ -10127,27 +11306,148 @@ } } } - } - }, - "components": { - "responses": { - "InvalidRequest": { - "description": "The request could not be validated", - "content": { - "application/json": { + }, + "/v1/portal/mobile-config": { + "get": { + "operationId": "getMobileConfig", + "summary": "getMobileConfig", + "description": "Returns the portal's mobile app configuration. By default the response is build-ready (resolved): base info (display_name from the portal name, app_host from the domain, environment) and branding (logo from the portal images, colors from the design palette) are filled in. Pass raw=true to get only the stored mobile_config without resolution.", + "tags": [ + "ECP Admin" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "portal_id", + "required": true, "schema": { - "$ref": "#/components/schemas/ErrorResp" + "type": "string" + }, + "description": "Portal ID" + }, + { + "in": "query", + "name": "raw", + "required": false, + "schema": { + "type": "boolean" + }, + "description": "Return only the stored mobile_config without resolving base info/branding." + } + ], + "responses": { + "200": { + "description": "Mobile config retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MobileConfig" + } + } } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } }, - "Unauthorized": { - "description": "Could not authenticate the user", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResp" - } + "put": { + "operationId": "putMobileConfig", + "summary": "putMobileConfig", + "description": "Merges the provided fields into the portal's mobile app configuration\n(deep merge). Only mobile_config is modified; all other portal settings\nare left untouched.\n", + "tags": [ + "ECP Admin" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "portal_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Portal ID" + } + ], + "requestBody": { + "description": "Editable mobile fields to merge into the existing mobile_config. Only mobile-relevant settings + app branding are applied; other fields are ignored.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MobileConfigUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Mobile config updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MobileConfig" + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + } + }, + "components": { + "responses": { + "InvalidRequest": { + "description": "The request could not be validated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "Unauthorized": { + "description": "Could not authenticate the user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } } } }, @@ -10264,102 +11564,441 @@ } } }, - "InvalidRequestCreateMeterReading": { - "description": "The request could not be validated", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResp" - }, - { - "properties": { - "reason": { - "type": "string", - "enum": [ - "contract_period", - "no_counter", - "no_direction", - "timestamp_future", - "less_than_previous", - "greater_than_subsequent", - "meter_decommissioned", - "plausibility_check_failed" - ] - } - } - }, - { - "oneOf": [ - { - "properties": { - "reason": { - "enum": [ - "plausibility_check_failed" - ] - }, - "upper_limit": { - "type": "number" - }, - "lower_limit": { - "type": "number" - } - }, - "required": [ - "upper_limit", - "lower_limit" - ] - }, - { - "not": { - "properties": { - "reason": { - "enum": [ - "plausibility_check_failed" - ] - } - } - } - } - ] - } - ] - } + "InvalidRequestCreateMeterReading": { + "description": "The request could not be validated", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResp" + }, + { + "properties": { + "reason": { + "type": "string", + "enum": [ + "contract_period", + "no_counter", + "no_direction", + "timestamp_future", + "less_than_previous", + "greater_than_subsequent", + "meter_decommissioned", + "plausibility_check_failed" + ] + } + } + }, + { + "oneOf": [ + { + "properties": { + "reason": { + "enum": [ + "plausibility_check_failed" + ] + }, + "upper_limit": { + "type": "number" + }, + "lower_limit": { + "type": "number" + } + }, + "required": [ + "upper_limit", + "lower_limit" + ] + }, + { + "not": { + "properties": { + "reason": { + "enum": [ + "plausibility_check_failed" + ] + } + } + } + } + ] + } + ] + } + } + } + } + }, + "securitySchemes": { + "PortalAuth": { + "type": "http", + "scheme": "bearer", + "description": "Portal Cognito Token" + }, + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "description": "Epilot Bearer Token" + }, + "EitherAuth": { + "type": "http", + "scheme": "bearer", + "description": "Portal or Epilot Bearer Token" + }, + "AsOrganization": { + "type": "apiKey", + "in": "header", + "name": "x-ivy-org-id", + "description": "Set organization id as internal user" + }, + "ExternalOIDCAuth": { + "type": "http", + "scheme": "bearer", + "description": "Authorization header with id token from external OIDC provider", + "bearerFormat": "JWT" + } + }, + "schemas": { + "PortalNotification": { + "type": "object", + "description": "A 360 notification addressed to a portal user.", + "required": [ + "id", + "read" + ], + "properties": { + "id": { + "type": "string", + "description": "Stable string identifier of the notification (the numeric notification id as a string).", + "example": "1234567890" + }, + "notification_id": { + "type": "number", + "description": "Numeric id of the notification, used to mark it as read.", + "example": 1234567890 + }, + "type": { + "type": "string", + "description": "Type of notification.", + "example": "workflow_step_overdue" + }, + "title": { + "type": "object", + "description": "Localized, already-rendered notification title.", + "properties": { + "en": { + "type": "string" + }, + "de": { + "type": "string" + } + } + }, + "message": { + "type": "object", + "description": "Localized, already-rendered notification message.", + "properties": { + "en": { + "type": "string" + }, + "de": { + "type": "string" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the notification was created." + }, + "read": { + "type": "boolean", + "description": "Whether the notification has been read by the user.", + "example": false + }, + "redirect_url": { + "type": "string", + "description": "Optional URL the notification points to." + } + } + }, + "MobileBuildStatus": { + "type": "object", + "description": "Latest build/upload status for a platform (system-written).", + "additionalProperties": true, + "properties": { + "version": { + "type": "string" + }, + "build_number": { + "type": "integer" + }, + "track": { + "type": "string", + "description": "e.g. testflight | internal | beta" + }, + "status": { + "type": "string", + "enum": [ + "building", + "uploaded", + "failed" + ] + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + } + } + }, + "MobileBranding": { + "type": "object", + "additionalProperties": true, + "properties": { + "app_icon": { + "type": "string" + }, + "splash": { + "type": "string" + }, + "splash_dark": { + "type": "string" + }, + "icon_background_color": { + "type": "string" + }, + "splash_background_color": { + "type": "string" + }, + "splash_background_color_dark": { + "type": "string" + } + } + }, + "MobileConfig": { + "type": "object", + "description": "Mobile app configuration for the portal. Stored inside the portal's config object. Identifiers/branding are non-secret; signing credentials live in a secure store, never here.", + "additionalProperties": true, + "properties": { + "portal_id": { + "type": "string", + "description": "Portal id (response-only; ignored on write)." + }, + "enabled": { + "type": "boolean" + }, + "display_name": { + "type": "string", + "description": "App display name compiled into the binary." + }, + "app_host": { + "type": "string", + "description": "Host the mobile shell loads (defaults to the portal domain)." + }, + "environment": { + "type": "string", + "enum": [ + "prod", + "staging", + "dev" + ] + }, + "branding": { + "$ref": "#/components/schemas/MobileBranding" + }, + "ios": { + "type": "object", + "additionalProperties": true, + "properties": { + "bundle_id": { + "type": "string", + "description": "iOS bundle id (matches the App Store Connect app)." + }, + "team_id": { + "type": "string", + "description": "Apple Developer Team ID." + }, + "credentials_status": { + "type": "string", + "enum": [ + "not_configured", + "configured" + ] + }, + "app_store_id": { + "type": "string", + "description": "Numeric App Store id (system-written after first upload)." + }, + "store_url": { + "type": "string", + "description": "System-written App Store URL." + }, + "last_build": { + "$ref": "#/components/schemas/MobileBuildStatus" + } + } + }, + "android": { + "type": "object", + "additionalProperties": true, + "properties": { + "package_name": { + "type": "string", + "description": "Android package name (matches the Play Console app)." + }, + "credentials_status": { + "type": "string", + "enum": [ + "not_configured", + "configured" + ] + }, + "upload_key_status": { + "type": "string", + "description": "Play App Signing upload-key state.", + "enum": [ + "not_configured", + "generated", + "enrolled" + ] + }, + "store_url": { + "type": "string", + "description": "System-written Play Store URL." + }, + "last_build": { + "$ref": "#/components/schemas/MobileBuildStatus" + } + } + }, + "ota": { + "$ref": "#/components/schemas/MobileOtaConfig" + } + } + }, + "MobileConfigUpdate": { + "type": "object", + "description": "Editable mobile fields for PUT. Only mobile-relevant settings + app branding can be changed. Portal-derived values (display_name, app_host), the portal logo, and system-written fields (credentials_status, app_store_id, last_build, …) are ignored if sent.", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "ios": { + "type": "object", + "additionalProperties": true, + "properties": { + "bundle_id": { + "type": "string" + }, + "team_id": { + "type": "string" + }, + "store_url": { + "type": "string" + }, + "app_store_id": { + "type": "string" + } + } + }, + "android": { + "type": "object", + "additionalProperties": true, + "properties": { + "package_name": { + "type": "string" + }, + "store_url": { + "type": "string" + } + } + }, + "branding": { + "$ref": "#/components/schemas/MobileBranding" + }, + "ota": { + "$ref": "#/components/schemas/MobileOtaConfig" + } + } + }, + "MobileOtaConfig": { + "type": "object", + "description": "OTA (over-the-air) update settings for the portal's mobile app. Drives the OTA build pipeline and the per-portal manifest. channel / update_strategy / min_native_version are epilot-internal controls.", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether OTA updates are enabled for this portal." + }, + "channel": { + "type": "string", + "enum": [ + "canary", + "stable" + ], + "description": "Release channel this portal follows." + }, + "auto_update": { + "type": "boolean", + "description": "Whether the app auto-updates or prompts the user." + }, + "update_strategy": { + "type": "string", + "enum": [ + "next-launch", + "immediate" + ], + "description": "When to apply a downloaded bundle." + }, + "min_native_version": { + "type": "string", + "description": "Minimum native app version required to load OTA bundles." } } - } - }, - "securitySchemes": { - "PortalAuth": { - "type": "http", - "scheme": "bearer", - "description": "Portal Cognito Token" - }, - "EpilotAuth": { - "type": "http", - "scheme": "bearer", - "description": "Epilot Bearer Token" - }, - "EitherAuth": { - "type": "http", - "scheme": "bearer", - "description": "Portal or Epilot Bearer Token" }, - "AsOrganization": { - "type": "apiKey", - "in": "header", - "name": "x-ivy-org-id", - "description": "Set organization id as internal user" + "OtaPortal": { + "type": "object", + "description": "A portal that has mobile OTA updates enabled.", + "required": [ + "domain", + "channel", + "autoUpdate", + "updateStrategy" + ], + "properties": { + "domain": { + "type": "string", + "description": "Portal hostname — the OTA manifest filename ({domain}.json).", + "example": "kundenportal.twl.de" + }, + "channel": { + "type": "string", + "enum": [ + "canary", + "stable" + ] + }, + "autoUpdate": { + "type": "boolean" + }, + "updateStrategy": { + "type": "string", + "enum": [ + "next-launch", + "immediate" + ] + }, + "minNativeVersion": { + "type": "string", + "example": "1.0.0" + } + } }, - "ExternalOIDCAuth": { - "type": "http", - "scheme": "bearer", - "description": "Authorization header with id token from external OIDC provider", - "bearerFormat": "JWT" - } - }, - "schemas": { "ContextEntity": { "type": "object", "description": "An entity reference for context-aware operations", @@ -10400,6 +12039,10 @@ "message": { "type": "string", "description": "Error message" + }, + "reason": { + "type": "string", + "description": "Machine-readable cause, when the API can name one. `PORTAL_USER_PENDING_ACTIVATION` means the portal user is authenticated but not mapped to a contact yet, so re-authenticating will not help." } } }, @@ -10815,7 +12458,7 @@ }, "identity_providers": { "type": "array", - "description": "SSO identity providers for the portal. When sent on a portal save (PUT/POST),\nthe list is fully synced — incoming providers are upserted and any existing\nproviders not in the list are deleted. Omit the field to leave SSO\nconfiguration unchanged; send an empty array to remove all providers.\n\nEach provider is persisted verbatim — `oidc_config.client_secret` is stored\nas sent. Customers are encouraged to reference an org env secret via\n`{{ env.VAR }}` rather than embed raw values.\n", + "description": "SSO identity providers for the portal. When sent on a portal save (PUT/POST),\nthe list is fully synced — incoming providers are upserted and any existing\nproviders not in the list are deleted. Omit the field to leave SSO\nconfiguration unchanged; send an empty array to remove all providers.\n\nSecrets: a provider sent without `oidc_config.client_secret` keeps the\nstored secret for the same slug; an explicit empty string clears it.\n`getPortalConfigV3` returns providers with raw secrets redacted;\n`{{ env.VAR }}` references pass through.\n", "items": { "$ref": "#/components/schemas/ProviderConfig" } @@ -10825,6 +12468,10 @@ "CommonConfigAttributes": { "type": "object", "properties": { + "mobile_config": { + "description": "Mobile app configuration (top-level; moved out of the config blob).", + "$ref": "#/components/schemas/MobileConfig" + }, "enabled": { "type": "boolean", "description": "Enable/Disable the portal access" @@ -10969,6 +12616,10 @@ "auto_redirect_to_sso": { "type": "boolean", "description": "Decide whether to automatically redirect to the provider page during login, which would completely bypass showing the portal authentication page." + }, + "prevent_user_enumeration": { + "type": "boolean", + "description": "Opt-in. When true, suppresses responses that reveal whether a user exists for public, pre-authentication actions (the login entry-point check and self-registration), at the expense of some UX. Already-authenticated actions are unaffected. Default false.\n" } } }, @@ -11066,6 +12717,13 @@ "type": "boolean", "example": true, "description": "Require symbols" + }, + "password_history_size": { + "type": "integer", + "minimum": 0, + "maximum": 24, + "example": 3, + "description": "Number of previous passwords a user is prevented from reusing. Set to 0 to disable reuse prevention. Maps to Cognito's PasswordHistorySize and requires the user pool to be on the Essentials or Plus feature plan." } } } @@ -11380,6 +13038,49 @@ "additionalProperties": { "$ref": "#/components/schemas/Block" } + }, + "notification_triggers": { + "type": "array", + "description": "Configures which 360 events generate an in-app notification for the portal user. Each enabled trigger renders the referenced notification template and creates a notification addressed to the portal user. Admin/builder-only — never exposed via the public portal config.", + "items": { + "$ref": "#/components/schemas/NotificationTriggerConfig" + } + }, + "engagement_center_enabled": { + "type": "boolean", + "description": "Master toggle for the portal's engagement center (in-app notifications including the notification triggers above). Off when absent — portal users only see the engagement center and receive trigger notifications after an admin enables it." + } + } + }, + "NotificationTriggerConfig": { + "type": "object", + "required": [ + "trigger_type" + ], + "properties": { + "trigger_type": { + "type": "string", + "description": "The 360 event that fires this notification trigger.", + "enum": [ + "entity_created", + "entity_assigned", + "workflow_step_overdue" + ] + }, + "entity_schema": { + "type": "string", + "description": "For `entity_created` / `entity_assigned` triggers, the entity schema slug (e.g. `opportunity`, `order`) whose creation or assignment fires this trigger. Ignored for other trigger types.", + "example": "opportunity" + }, + "enabled": { + "type": "boolean", + "description": "Whether this trigger is active.", + "default": true + }, + "template_id": { + "type": "string", + "format": "uuid", + "description": "Entity id of the notification_template to render for this trigger." } } }, @@ -11854,6 +13555,11 @@ "contract_number": "123456" } } + }, + "trigger_identifiers_check": { + "type": "boolean", + "default": true, + "description": "Whether to (re)trigger the registration identifiers check hook, which issues a request\nto the connected ERP to (re)sync the contact, in addition to waiting for the entity to\narrive. Defaults to true to preserve existing behaviour. Set to false on retry attempts\nto only poll for an already-triggered sync to land, without issuing another upstream\nrequest to the ERP.\n" } } }, @@ -12241,9 +13947,55 @@ }, "templates": { "type": "object", + "deprecated": true, + "description": "DEPRECATED — client-supplied Handlebars templates. Use `templates_ref` instead so templates are derived server-side from admin-authored portal configuration. Ignored when `templates_ref` is present; rejected once the org has the `portals-reject-client-templates` flag enabled.", "additionalProperties": { "type": "string" } + }, + "templates_ref": { + "type": "object", + "description": "Reference to admin-authored portal configuration (a page block or a global search configuration item) from which the API derives Handlebars templates server-side. This replaces client-supplied template strings so portal users can never submit arbitrary templates for resolution. When both a reference and raw `templates` are provided, the reference wins and the raw templates are ignored.", + "properties": { + "page_id": { + "type": "string", + "description": "ID of the portal page to derive templates from. When given without `block_id`, templates are derived from every block of the page and returned resolved as a nested map keyed by block id (the entity detail page contract)." + }, + "block_id": { + "type": "string", + "description": "ID of a block within the page. Templates are derived from the block's content according to its block type (e.g. meter_selector, meter_reading, entity_list)." + }, + "config_id": { + "type": "string", + "description": "For blocks carrying a per-schema configuration array (entity_list), the id of the configuration item to derive templates from. Requires `page_id` and `block_id`." + }, + "global_search_config_id": { + "type": "string", + "description": "ID of the portal's `global_search` configuration item to derive search result templates and the group title template from. Mutually exclusive with `page_id`." + } + } + } + } + }, + "TemplatesRef": { + "type": "object", + "description": "Reference to admin-authored portal configuration (a page block or a global search configuration item) from which the API derives Handlebars templates server-side. This replaces client-supplied template strings so portal users can never submit arbitrary templates for resolution. When both a reference and raw `templates` are provided, the reference wins and the raw templates are ignored.", + "properties": { + "page_id": { + "type": "string", + "description": "ID of the portal page to derive templates from. When given without `block_id`, templates are derived from every block of the page and returned resolved as a nested map keyed by block id (the entity detail page contract)." + }, + "block_id": { + "type": "string", + "description": "ID of a block within the page. Templates are derived from the block's content according to its block type (e.g. meter_selector, meter_reading, entity_list)." + }, + "config_id": { + "type": "string", + "description": "For blocks carrying a per-schema configuration array (entity_list), the id of the configuration item to derive templates from. Requires `page_id` and `block_id`." + }, + "global_search_config_id": { + "type": "string", + "description": "ID of the portal's `global_search` configuration item to derive search result templates and the group title template from. Mutually exclusive with `page_id`." } } }, @@ -13165,11 +14917,50 @@ "is_new": { "type": "boolean", "description": "Indicate whether the user has not seen/downloaded the file before" + }, + "custom_download_url_auth": { + "type": "string", + "enum": [ + "token", + "presigned" + ], + "description": "Authorization mode of an externally hosted file's download url. `presigned` (the default) means `public_url` is HMAC-signed and needs no auth header; `token` means it authorizes the caller's bearer token instead and must be fetched with an `Authorization` header. Because `presigned` is the default, the absence of this property does NOT prove the file is stored in epilot — an external file may omit it." } } } ] }, + "FilePreviewResult": { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "description": "What kind of preview (if any) is available. \"pdf\" covers both native PDFs and Office documents that were converted to PDF server-side. \"unsupported\" means no preview is available and the caller should fall back to download.", + "enum": [ + "pdf", + "image", + "unsupported" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "A Content-Disposition: inline URL to render in a preview viewer. Present only when kind is \"pdf\" or \"image\". Short-lived — do not cache across dialog opens." + }, + "requires_auth": { + "type": "boolean", + "description": "When true, `url` only answers to the portal user's bearer token: fetch the bytes with an `Authorization: Bearer ` header and render them from an object URL. Only ever set for URLs on an allow-listed ERP file-proxy host." + }, + "download_url": { + "type": "string", + "format": "uri", + "description": "Optional download URL for the file, returned for any kind — including \"unsupported\" — whenever the file entity exposes one. It is only a Content-Disposition: attachment URL for files with access_control \"private\", where it is a short-lived signed URL; for any other access level it is the file entity's plain public/CDN object URL, which carries no attachment disposition and may render inline in the browser. Absent when the file entity exposes no usable URL, so clients must handle it being missing." + } + } + }, "EntityFileCount": { "type": "object", "required": [ @@ -13407,13 +15198,64 @@ }, "description": "Seamless link identifier in a form of [extensionId, linkId]" } - }, - "required": [ - "id", - "label", - "type", - "link" - ] + }, + "required": [ + "id", + "label", + "type", + "link" + ] + }, + "OutstandingTask": { + "type": "object", + "required": [ + "entity_id", + "entity_schema", + "entity_title", + "workflow_id", + "workflow_name", + "step_id", + "step_name", + "journey_id" + ], + "properties": { + "entity_id": { + "type": "string", + "description": "ID of the entity the task belongs to" + }, + "entity_schema": { + "type": "string", + "description": "Schema slug of the entity (opportunity, order or contract)" + }, + "entity_title": { + "type": "string", + "description": "Title (_title) of the entity" + }, + "workflow_id": { + "type": "string", + "description": "Workflow execution id" + }, + "workflow_name": { + "type": "string", + "description": "Workflow execution / template name" + }, + "step_id": { + "type": "string", + "description": "Task (step) id within the workflow execution" + }, + "step_name": { + "type": "string", + "description": "Task (step) name" + }, + "journey_id": { + "type": "string", + "description": "Journey id the customer needs to fill out" + }, + "complete_task_automatically": { + "type": "boolean", + "description": "Whether submitting the journey from the portal should auto-complete the task. When false, an internal user completes it." + } + } }, "WorkflowExecution": { "type": "object", @@ -13493,6 +15335,234 @@ "completedTime": "2024-01-13T05:18:43.827Z" } }, + "PortalWorkflow": { + "type": "object", + "description": "A portal-facing projection of a workflow execution (V1 or V2), with the execution\ngraph already linearized by the Workflows API into a flat, ordered list of\nportal-visible tasks.\n", + "properties": { + "id": { + "type": "string", + "description": "Id of the workflow / flow execution" + }, + "definition_id": { + "type": "string", + "description": "Id of the workflow definition / flow template this execution was created from" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "STARTED", + "DONE", + "CLOSED" + ] + }, + "version": { + "type": "integer", + "description": "2 = legacy V1 workflow execution (linear model), 3 = V2 flow execution (graph model)", + "enum": [ + 2, + 3 + ] + }, + "created_at": { + "type": "string", + "description": "Creation timestamp, doubles as started time" + }, + "updated_at": { + "type": "string", + "description": "Last update timestamp" + }, + "completed_at": { + "type": "string", + "description": "Timestamp when the execution was completed, if it is" + }, + "due_date": { + "type": "string" + }, + "assigned_to": { + "type": "array", + "items": { + "type": "string" + } + }, + "contexts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "entity_id": { + "type": "string" + }, + "entity_schema": { + "type": "string" + }, + "is_primary": { + "type": "boolean" + } + } + } + }, + "is_path_complete": { + "type": "boolean", + "description": "True when the linearized path reached the end of the execution graph. False when an\nunevaluated decision task was encountered, meaning additional tasks may appear once\nthe decision is resolved (the returned tasks are still the guaranteed active path).\nAlways true for V1 executions.\n" + }, + "tasks": { + "type": "array", + "description": "Portal-visible tasks in linear (timeline) order", + "items": { + "$ref": "#/components/schemas/PortalWorkflowTask" + } + } + }, + "required": [ + "id", + "name", + "status", + "version", + "is_path_complete", + "tasks" + ] + }, + "PortalWorkflowTask": { + "type": "object", + "description": "A single portal-visible task of a linearized workflow execution", + "properties": { + "id": { + "type": "string", + "description": "Id of the underlying task (V2) or step (V1)" + }, + "name": { + "type": "string", + "description": "Internal task/step name (fallback label when the portal config has none)" + }, + "order": { + "type": "integer", + "description": "Zero-based position of the task in the linearized timeline" + }, + "status": { + "type": "string", + "enum": [ + "COMPLETED", + "SKIPPED", + "IN_PROGRESS", + "PENDING" + ], + "description": "Normalized task status:\n- COMPLETED / SKIPPED: the task is done (skipped tasks count as done for progress)\n- IN_PROGRESS: the task has been reached and work has started\n- PENDING: the task has not been completed yet\n" + }, + "is_active": { + "type": "boolean", + "description": "True when the task has been reached in the execution — all predecessor tasks on its\npath are completed/skipped — so the portal user can act on it (e.g. start its journey).\nTasks that are only part of the projected future path are returned with is_active false.\n" + }, + "ecp": { + "$ref": "#/components/schemas/PortalTaskConfig" + }, + "installer": { + "$ref": "#/components/schemas/PortalTaskConfig" + }, + "journey": { + "type": "object", + "description": "Journey linked to the task, if any", + "properties": { + "id": { + "type": "string" + }, + "journeyId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "complete_task_automatically": { + "type": "boolean", + "description": "If true, the task is auto completed when the journey is completed" + } + } + }, + "assigned_to": { + "type": "array", + "items": { + "type": "string" + } + }, + "phase_id": { + "type": "string", + "description": "Id of the phase the underlying task belongs to, if any (V2 only)" + }, + "phase_name": { + "type": "string", + "description": "Name of the phase the underlying task belongs to, if any (V2 only)" + }, + "completed_at": { + "type": "string", + "description": "Timestamp when the task was completed or skipped" + }, + "updated_at": { + "type": "string", + "description": "Last update timestamp of the underlying task/step" + } + }, + "required": [ + "id", + "name", + "order", + "status", + "is_active" + ] + }, + "EntityPortalWorkflows": { + "type": "object", + "description": "Linearized portal workflows of a single entity", + "properties": { + "entity_id": { + "type": "string" + }, + "portal_workflows": { + "type": "array", + "description": "Empty when the entity has no portal-relevant workflow", + "items": { + "$ref": "#/components/schemas/PortalWorkflow" + } + } + }, + "required": [ + "entity_id", + "portal_workflows" + ] + }, + "PortalTaskConfig": { + "type": "object", + "description": "Portal-specific (ECP / installer) display config of a workflow task", + "properties": { + "enabled": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "journey": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "journeyId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "complete_task_automatically": { + "type": "boolean" + } + } + } + } + }, "BaseBillingEvent": { "allOf": [ { @@ -13767,7 +15837,8 @@ }, "templates": { "type": "object", - "description": "Template strings to parse and return as synthetic fields. Supports both string values and nested objects of strings.", + "deprecated": true, + "description": "DEPRECATED — client-supplied template strings to parse and return as synthetic fields. Supports both string values and nested objects of strings. Use `templates_ref` instead so templates are derived server-side from admin-authored portal configuration; ignored when `templates_ref` is present and rejected once the org has the `portals-reject-client-templates` flag enabled.", "additionalProperties": { "oneOf": [ { @@ -13791,6 +15862,10 @@ } } }, + "templates_ref": { + "$ref": "#/components/schemas/TemplatesRef", + "description": "Reference to the portal page (or a single block within it) whose admin-configured content provides the templates, derived server-side. With only `page_id`, the derived templates are a nested map keyed by block id, mirroring the entity detail page contract." + }, "filters": { "type": "array", "description": "Additional filters to apply to the search query", @@ -13907,7 +15982,8 @@ }, "group_title": { "type": "string", - "description": "Template for group title using variables", + "deprecated": true, + "description": "DEPRECATED — client-supplied Handlebars template for the group title. Use `templates_ref` (global_search_config_id) instead; overridden when `templates_ref` derives a group title and rejected once the org has the `portals-reject-client-templates` flag enabled.", "example": "{{customer[Primary].first_name}} {{customer[Primary].last_name}}" }, "group_size": { @@ -13974,7 +16050,8 @@ }, "templates": { "type": "object", - "description": "Template strings to parse and return as synthetic fields", + "deprecated": true, + "description": "DEPRECATED — client-supplied template strings to parse and return as synthetic fields. Use `templates_ref` instead; ignored when `templates_ref` is present and rejected once the org has the `portals-reject-client-templates` flag enabled.", "additionalProperties": { "type": "string" }, @@ -13984,6 +16061,10 @@ "content_bottom_name": "{{custom_contract_delivery_address}}" } }, + "templates_ref": { + "$ref": "#/components/schemas/TemplatesRef", + "description": "Reference to admin-authored configuration providing the templates, derived server-side. For the global search block pass `global_search_config_id`; the derived templates also supply the group title template (overriding `group_title`)." + }, "filters": { "type": "array", "description": "Additional filters to apply to the search query", @@ -14272,16 +16353,130 @@ } } }, - "meterReadingPlausibilityCheck": { - "type": "object", - "properties": { - "extension": { - "$ref": "#/components/schemas/PublicExtensionDetails" - }, - "hook": { - "$ref": "#/components/schemas/PublicMeterReadingPlausibilityCheckDetails" - } - } + "meterReadingPlausibilityCheck": { + "type": "object", + "properties": { + "extension": { + "$ref": "#/components/schemas/PublicExtensionDetails" + }, + "hook": { + "$ref": "#/components/schemas/PublicMeterReadingPlausibilityCheckDetails" + } + } + }, + "changeEmail": { + "type": "object", + "properties": { + "app": { + "$ref": "#/components/schemas/PublicAppDetails" + }, + "extension": { + "$ref": "#/components/schemas/PublicExtensionDetails" + }, + "hook": { + "$ref": "#/components/schemas/PublicChangeEmailDetails" + } + } + }, + "changePassword": { + "type": "object", + "properties": { + "app": { + "$ref": "#/components/schemas/PublicAppDetails" + }, + "extension": { + "$ref": "#/components/schemas/PublicExtensionDetails" + }, + "hook": { + "$ref": "#/components/schemas/PublicChangePasswordDetails" + } + } + }, + "deleteAccount": { + "type": "object", + "properties": { + "app": { + "$ref": "#/components/schemas/PublicAppDetails" + }, + "extension": { + "$ref": "#/components/schemas/PublicExtensionDetails" + }, + "hook": { + "$ref": "#/components/schemas/PublicDeleteAccountDetails" + } + } + } + } + }, + "PublicSelfManagementExplanation": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Explanation of the functionality shown to the end user." + } + }, + "additionalProperties": { + "type": "string", + "description": "Explanation of the functionality in language denoted by ISO 3166-1 alpha-2 code." + }, + "required": [ + "en" + ], + "description": "Explanation of the hook." + }, + "PublicChangeEmailDetails": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the hook." + }, + "change_mode": { + "type": "string", + "enum": [ + "synchronous", + "asynchronous" + ], + "description": "Whether the third-party system applies the email change immediately (`synchronous`) or takes it over entirely (`asynchronous`).\nIn synchronous mode the login email is changed as soon as the request succeeds and the portal user has to sign in again with the new address.\n", + "default": "asynchronous" + }, + "require_password_confirmation": { + "type": "boolean", + "description": "Whether the portal user must confirm their current password before the email change is handed over to the third-party system.\nNot required for portal users whose identity is managed by an identity provider - an SSO login has no password to confirm.\n", + "default": true + }, + "explanation": { + "$ref": "#/components/schemas/PublicSelfManagementExplanation" + } + } + }, + "PublicChangePasswordDetails": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the hook." + }, + "require_new_password": { + "type": "boolean", + "description": "Whether the portal user must provide a new password that is passed to the third-party system.", + "default": false + }, + "explanation": { + "$ref": "#/components/schemas/PublicSelfManagementExplanation" + } + } + }, + "PublicDeleteAccountDetails": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the hook." + }, + "explanation": { + "$ref": "#/components/schemas/PublicSelfManagementExplanation" } } }, @@ -14409,7 +16604,8 @@ "PT15M", "PT1H", "P1D", - "P1M" + "P1M", + "P1Y" ] } }, @@ -14694,6 +16890,15 @@ }, { "$ref": "#/components/schemas/ExtensionHookVisualizationMetadata" + }, + { + "$ref": "#/components/schemas/ExtensionHookChangeEmail" + }, + { + "$ref": "#/components/schemas/ExtensionHookChangePassword" + }, + { + "$ref": "#/components/schemas/ExtensionHookDeleteAccount" } ] } @@ -15094,9 +17299,272 @@ }, "lower_limit": { "type": "string", - "description": "Lower allowed limit of the meter reading", - "example": "{{CallResponse.data.lower_limit}}" + "description": "Lower allowed limit of the meter reading", + "example": "{{CallResponse.data.lower_limit}}" + }, + "error_message_path": { + "type": "string", + "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", + "example": "error.message" + } + } + }, + "use_static_ips": { + "type": "boolean", + "deprecated": true, + "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", + "default": false + }, + "secure_proxy": { + "$ref": "#/components/schemas/SecureProxyConfig" + } + }, + "required": [ + "type", + "call", + "resolved" + ] + }, + "ExtensionHookPriceDataRetrieval": { + "description": "Hook that will allow using the specified source as data for price visualizations. This hook is triggered to fetch the data. Format of the request and response has to follow the following specification: TBD. The expected response to the call is:\n - 200 with the time series data\n", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "priceDataRetrieval" + ] + }, + "auth": { + "$ref": "#/components/schemas/ExtensionAuthBlock" + }, + "call": { + "type": "object", + "properties": { + "method": { + "type": "string", + "description": "HTTP method to use for the call", + "default": "GET" + }, + "url": { + "type": "string", + "description": "URL to call. Supports variable interpolation." + }, + "params": { + "type": "object", + "description": "Parameters to append to the URL. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "headers": { + "type": "object", + "description": "Headers to use. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "body": { + "type": "object", + "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", + "additionalProperties": { + "type": "string" + }, + "default": {} + } + }, + "required": [ + "url" + ] + }, + "resolved": { + "type": "object", + "properties": { + "data_path": { + "type": "string", + "description": "Optional path to the data (array) in the response. If omitted, the data is assumed to be on the top level." + }, + "dataPath": { + "type": "string", + "deprecated": true, + "description": "Deprecated. Use `data_path` instead." + }, + "error_message_path": { + "type": "string", + "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", + "example": "error.message" + } + } + }, + "use_static_ips": { + "type": "boolean", + "deprecated": true, + "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", + "default": false + }, + "secure_proxy": { + "$ref": "#/components/schemas/SecureProxyConfig" + } + }, + "required": [ + "type", + "call" + ] + }, + "ExtensionHookConsumptionDataRetrieval": { + "description": "Hook that will allow using the specified source as data for consumption visualizations. This hook is triggered to fetch the data. Format of the request and response has to follow the following specification: TBD. The expected response to the call is:\n - 200 with the time series data\n", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "consumptionDataRetrieval" + ] + }, + "auth": { + "$ref": "#/components/schemas/ExtensionAuthBlock" + }, + "call": { + "type": "object", + "properties": { + "method": { + "type": "string", + "description": "HTTP method to use for the call", + "default": "GET" + }, + "url": { + "type": "string", + "description": "URL to call. Supports variable interpolation." + }, + "params": { + "type": "object", + "description": "Parameters to append to the URL. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "headers": { + "type": "object", + "description": "Headers to use. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "body": { + "type": "object", + "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", + "additionalProperties": { + "type": "string" + }, + "default": {} + } + }, + "required": [ + "url" + ] + }, + "resolved": { + "type": "object", + "properties": { + "data_path": { + "type": "string", + "description": "Optional path to the data (array) in the response. If omitted, the data is assumed to be on the top level." + }, + "dataPath": { + "type": "string", + "deprecated": true, + "description": "Deprecated. Use `data_path` instead." + }, + "error_message_path": { + "type": "string", + "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", + "example": "error.message" + } + } + }, + "use_static_ips": { + "type": "boolean", + "deprecated": true, + "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", + "default": false + }, + "secure_proxy": { + "$ref": "#/components/schemas/SecureProxyConfig" + } + }, + "required": [ + "type", + "call" + ] + }, + "ExtensionHookDataExport": { + "description": "Generic data export hook. When configured on a visualization block, the portal delegates the export action (e.g. CSV/Excel/PDF download) to the configured external source instead of generating the file itself. Can be used by any block that supports export — consumption charts, dynamic tariff charts, etc. The expected response to the call is:\n - 200 with a JSON body describing the exported file (download_url, optional filename, content_type, expires_at)\n", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "dataExport" + ] + }, + "block_types": { + "type": "array", + "description": "Optional list of portal block types this hook supports. If omitted,\nthe hook is usable on any export-capable block. Allowed values match\nthe block type identifiers used by the portal builder\n(e.g. `consumption_visualization`, `dynamic_tariff`).\n", + "items": { + "type": "string" + } + }, + "auth": { + "$ref": "#/components/schemas/ExtensionAuthBlock" + }, + "call": { + "type": "object", + "properties": { + "method": { + "type": "string", + "description": "HTTP method to use for the call", + "default": "GET" + }, + "url": { + "type": "string", + "description": "URL to call. Supports variable interpolation." + }, + "params": { + "type": "object", + "description": "Parameters to append to the URL. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "headers": { + "type": "object", + "description": "Headers to use. Supports variable interpolation.", + "additionalProperties": { + "type": "string" + }, + "default": {} }, + "body": { + "type": "object", + "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", + "additionalProperties": { + "type": "string" + }, + "default": {} + } + }, + "required": [ + "url" + ] + }, + "resolved": { + "type": "object", + "properties": { "error_message_path": { "type": "string", "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", @@ -15116,18 +17584,17 @@ }, "required": [ "type", - "call", - "resolved" + "call" ] }, - "ExtensionHookPriceDataRetrieval": { - "description": "Hook that will allow using the specified source as data for price visualizations. This hook is triggered to fetch the data. Format of the request and response has to follow the following specification: TBD. The expected response to the call is:\n - 200 with the time series data\n", + "ExtensionHookVisualizationMetadata": { + "description": "Hook that returns runtime metadata describing how a visualization should be rendered for a given portal context. Invoked by the portal before fetching data, with the same context the data hook receives.\n", "type": "object", "properties": { "type": { "type": "string", "enum": [ - "priceDataRetrieval" + "visualizationMetadata" ] }, "auth": { @@ -15179,7 +17646,7 @@ "properties": { "data_path": { "type": "string", - "description": "Optional path to the data (array) in the response. If omitted, the data is assumed to be on the top level." + "description": "Optional path to the metadata object in the response. If omitted, the metadata is assumed to be on the top level." }, "dataPath": { "type": "string", @@ -15208,14 +17675,14 @@ "call" ] }, - "ExtensionHookConsumptionDataRetrieval": { + "ExtensionHookCostDataRetrieval": { "description": "Hook that will allow using the specified source as data for consumption visualizations. This hook is triggered to fetch the data. Format of the request and response has to follow the following specification: TBD. The expected response to the call is:\n - 200 with the time series data\n", "type": "object", "properties": { "type": { "type": "string", "enum": [ - "consumptionDataRetrieval" + "costDataRetrieval" ] }, "auth": { @@ -15296,22 +17763,47 @@ "call" ] }, - "ExtensionHookDataExport": { - "description": "Generic data export hook. When configured on a visualization block, the portal delegates the export action (e.g. CSV/Excel/PDF download) to the configured external source instead of generating the file itself. Can be used by any block that supports export — consumption charts, dynamic tariff charts, etc. The expected response to the call is:\n - 200 with a JSON body describing the exported file (download_url, optional filename, content_type, expires_at)\n", + "ExtensionHookChangeEmail": { + "description": "Hook that replaces the built-in change email functionality for portal users. When configured, the portal does not run its own change email flow. Instead, this hook makes an HTTP call to the third-party system, which is expected to handle the email change.\nThe `change_mode` controls what the portal does after the call:\n - `asynchronous`: The third-party system takes the email change over entirely (most likely by sending the user instructions to confirm the new email address). The portal does not change the login email itself.\n - `synchronous`: The third-party system applies the email change immediately. The portal waits for a successful (2xx) response and then also changes the portal user's login email right away, without sending a confirmation email. The user has to sign in again with the new email address afterwards.\n\nThe expected response http status code to the call is:\n - 2xx if the request was accepted\n - non-2xx if the request failed (optionally with a human-readable message resolved via `resolved.error_message_path`)\n", "type": "object", "properties": { "type": { "type": "string", "enum": [ - "dataExport" + "changeEmail" ] }, - "block_types": { - "type": "array", - "description": "Optional list of portal block types this hook supports. If omitted,\nthe hook is usable on any export-capable block. Allowed values match\nthe block type identifiers used by the portal builder\n(e.g. `consumption_visualization`, `dynamic_tariff`).\n", - "items": { - "type": "string" - } + "change_mode": { + "type": "string", + "enum": [ + "synchronous", + "asynchronous" + ], + "description": "Controls how the email change is handled once the third-party system accepted it. `asynchronous` hands the email change over entirely to the third-party system and the portal does not change the login email itself. `synchronous` waits for the third-party system to respond and then changes the portal user's login email immediately, without a confirmation email.\n", + "default": "asynchronous" + }, + "require_password_confirmation": { + "type": "boolean", + "description": "Whether the portal user must confirm their current password before the change email request is handed over to the third-party system. When true, the portal collects and verifies the current password before calling the hook. Skipped for portal users whose identity is managed by an identity provider: an SSO login has no password to confirm. When no password is confirmed and `change_mode` is `synchronous`, the re-created login gets a random password and the portal user signs in through their identity provider, or sets a new password via the email code flow.\n", + "default": true + }, + "explanation": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Explanation of the functionality shown to the end user.", + "example": "You will receive an email with instructions to confirm your new email address." + } + }, + "additionalProperties": { + "type": "string", + "description": "Explanation of the functionality in language denoted by ISO 3166-1 alpha-2 code." + }, + "required": [ + "en" + ], + "description": "Optional explanation shown to the user in the change email confirmation dialog." }, "auth": { "$ref": "#/components/schemas/ExtensionAuthBlock" @@ -15322,7 +17814,7 @@ "method": { "type": "string", "description": "HTTP method to use for the call", - "default": "GET" + "default": "POST" }, "url": { "type": "string", @@ -15346,15 +17838,12 @@ }, "body": { "type": "object", - "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", - "additionalProperties": { - "type": "string" - }, - "default": {} + "description": "Optional JSON body to use for the call. Defaults to an object with the requested new email and portal user context. The requested new email is available as `{{Input.new_email}}` and the current account email as `{{Input.old_email}}`. Supports variable interpolation." } }, "required": [ - "url" + "url", + "headers" ] }, "resolved": { @@ -15367,12 +17856,6 @@ } } }, - "use_static_ips": { - "type": "boolean", - "deprecated": true, - "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", - "default": false - }, "secure_proxy": { "$ref": "#/components/schemas/SecureProxyConfig" } @@ -15382,16 +17865,39 @@ "call" ] }, - "ExtensionHookVisualizationMetadata": { - "description": "Hook that returns runtime metadata describing how a visualization should be rendered for a given portal context. Invoked by the portal before fetching data, with the same context the data hook receives.\n", + "ExtensionHookChangePassword": { + "description": "Hook that replaces the built-in change password functionality for portal users. When configured, the portal does not change the user's password itself. Instead, this hook makes an HTTP call to the third-party system, which is expected to handle the password change (most likely by sending the user instructions to complete the process).\nThe expected response http status code to the call is:\n - 2xx if the request was accepted\n - non-2xx if the request failed (optionally with a human-readable message resolved via `resolved.error_message_path`)\n", "type": "object", "properties": { "type": { "type": "string", "enum": [ - "visualizationMetadata" + "changePassword" ] }, + "require_new_password": { + "type": "boolean", + "description": "Whether the portal user must provide a new password. When false, the portal only asks the user to confirm (showing the configured explanation) and no new password is collected; the third-party system is expected to handle the password change. When true, the portal collects a new password and passes it to the third-party system as `{{Input.new_password}}`.\n", + "default": false + }, + "explanation": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Explanation of the functionality shown to the end user.", + "example": "You will receive an email with instructions to reset your password." + } + }, + "additionalProperties": { + "type": "string", + "description": "Explanation of the functionality in language denoted by ISO 3166-1 alpha-2 code." + }, + "required": [ + "en" + ], + "description": "Optional explanation shown to the user in the change password confirmation dialog." + }, "auth": { "$ref": "#/components/schemas/ExtensionAuthBlock" }, @@ -15401,7 +17907,7 @@ "method": { "type": "string", "description": "HTTP method to use for the call", - "default": "GET" + "default": "POST" }, "url": { "type": "string", @@ -15425,29 +17931,17 @@ }, "body": { "type": "object", - "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", - "additionalProperties": { - "type": "string" - }, - "default": {} + "description": "Optional JSON body to use for the call. Defaults to an object with portal user context (and the new password as `{{Input.new_password}}` when `require_new_password` is true). Supports variable interpolation." } }, "required": [ - "url" + "url", + "headers" ] }, "resolved": { "type": "object", "properties": { - "data_path": { - "type": "string", - "description": "Optional path to the metadata object in the response. If omitted, the metadata is assumed to be on the top level." - }, - "dataPath": { - "type": "string", - "deprecated": true, - "description": "Deprecated. Use `data_path` instead." - }, "error_message_path": { "type": "string", "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", @@ -15455,12 +17949,6 @@ } } }, - "use_static_ips": { - "type": "boolean", - "deprecated": true, - "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", - "default": false - }, "secure_proxy": { "$ref": "#/components/schemas/SecureProxyConfig" } @@ -15470,16 +17958,53 @@ "call" ] }, - "ExtensionHookCostDataRetrieval": { - "description": "Hook that will allow using the specified source as data for consumption visualizations. This hook is triggered to fetch the data. Format of the request and response has to follow the following specification: TBD. The expected response to the call is:\n - 200 with the time series data\n", + "ExtensionHookDeleteAccount": { + "description": "Hook that replaces the built-in delete account functionality for portal users. When configured, the portal does not delete the user itself. Instead, this hook makes an HTTP call to the third-party system, which is expected to handle the deletion.\nThe `deletion_mode` controls what the portal does after the call:\n - `synchronous`: The third-party system deletes the user immediately. The portal waits for a successful (2xx) response and then also deletes the epilot user.\n - `asynchronous`: The third-party system handles deletion out-of-band. The portal does not delete anything immediately; cleanup is expected to happen later (e.g. via the user deletion API or webhooks).\n\nThe optional `delete_contact` additionally deletes the contact related to the portal user, once the portal user itself was deleted (`synchronous` mode only).\n\nThe expected response http status code to the call is:\n - 2xx if the request was accepted\n - non-2xx if the request failed (optionally with a human-readable message resolved via `resolved.error_message_path`)\n", "type": "object", "properties": { "type": { "type": "string", "enum": [ - "costDataRetrieval" + "deleteAccount" ] }, + "deletion_mode": { + "type": "string", + "enum": [ + "synchronous", + "asynchronous" + ], + "description": "Controls how the account deletion is handled. `synchronous` waits for the third-party system to respond and then also deletes the epilot user. `asynchronous` hands the deletion over entirely to the third-party system and the portal does not delete anything immediately.\n", + "default": "synchronous" + }, + "delete_contact": { + "type": "string", + "enum": [ + "none", + "soft", + "hard" + ], + "description": "Opt-in deletion of the contact related to the portal user, applied after the portal user itself was deleted. `none` (default) leaves the contact untouched. `soft` deletes the contact, so it can still be restored from the trash. `hard` permanently purges the contact. Only applied in `synchronous` deletion mode, as `asynchronous` mode hands the cleanup over to the third-party system.\n", + "default": "none" + }, + "explanation": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Explanation of the functionality shown to the end user.", + "example": "Your account deletion will be processed by our system. This may take a few days." + } + }, + "additionalProperties": { + "type": "string", + "description": "Explanation of the functionality in language denoted by ISO 3166-1 alpha-2 code." + }, + "required": [ + "en" + ], + "description": "Optional explanation shown to the user in the delete account confirmation dialog." + }, "auth": { "$ref": "#/components/schemas/ExtensionAuthBlock" }, @@ -15489,7 +18014,7 @@ "method": { "type": "string", "description": "HTTP method to use for the call", - "default": "GET" + "default": "POST" }, "url": { "type": "string", @@ -15513,29 +18038,17 @@ }, "body": { "type": "object", - "description": "Request body to send. Supports variable interpolation. Content format is determined by Content-Type header.", - "additionalProperties": { - "type": "string" - }, - "default": {} + "description": "Optional JSON body to use for the call. Defaults to an object with portal user context, e.g. `{\"portal_user_id\": \"...\", \"email\": \"...\"}`. Supports variable interpolation." } }, "required": [ - "url" + "url", + "headers" ] }, "resolved": { "type": "object", "properties": { - "data_path": { - "type": "string", - "description": "Optional path to the data (array) in the response. If omitted, the data is assumed to be on the top level." - }, - "dataPath": { - "type": "string", - "deprecated": true, - "description": "Deprecated. Use `data_path` instead." - }, "error_message_path": { "type": "string", "description": "Optional path to a human-readable error message in the third-party response body, used when the call fails (non-2xx status).\nIf specified and the path resolves to a string, that message is forwarded to the end user instead of a generic error.\n", @@ -15543,12 +18056,6 @@ } } }, - "use_static_ips": { - "type": "boolean", - "deprecated": true, - "description": "Deprecated. Prefer `secure_proxy` instead.\nIf true, requests are made from a set of static IP addresses and only allow connections to a set of allowed IP addresses. Get in touch with us to add your IP addresses.\n", - "default": false - }, "secure_proxy": { "$ref": "#/components/schemas/SecureProxyConfig" } @@ -15852,7 +18359,7 @@ "type": "string", "example": "office-365-login", "description": "URL-friendly slug to use as organization-unique identifier for Provider", - "pattern": "[0-9a-z-]+" + "pattern": "[0-9a-z_-]+" }, "ProviderDisplayName": { "type": "string", @@ -15886,6 +18393,11 @@ }, "mobile_oidc_config": { "$ref": "#/components/schemas/MoblieOIDCConfig" + }, + "expose_client_secret": { + "type": "boolean", + "description": "Allow the resolved `client_secret` to be returned through the\npublic single-provider endpoint at SSO initiation. Only set this\nfor OIDC flows that require a public client secret in the\nbrowser (e.g. some PKCE-less authorization-code variants). When\nunset (default), the secret is kept server-side and only used at\nthe token exchange.\n", + "example": false } }, "required": [ @@ -16198,7 +18710,6 @@ "token_endpoint", "grant_type", "code", - "redirect_uri", "client_id", "code_verifier" ], @@ -16208,7 +18719,8 @@ }, "token_endpoint": { "type": "string", - "description": "URL of the authorization endpoint", + "deprecated": true, + "description": "Deprecated and ignored; the token endpoint is derived server-side from the provider config.", "example": "https://www.facebook.com/v12.0/dialog/oauth" }, "grant_type": { @@ -16223,8 +18735,8 @@ }, "redirect_uri": { "type": "string", - "description": "The redirect uri", - "example": "https://customer-portal.com/login" + "description": "The redirect uri used in the authorization request. Optional: when omitted it is\nresolved server-side from the provider's configured `redirect_uri`, falling back to\nthe portal's own callback route (`https:///sso`).\n", + "example": "https://customer-portal.com/sso" }, "client_id": { "type": "string", @@ -16604,6 +19116,10 @@ "CommonConfigAttributesV3": { "type": "object", "properties": { + "mobile_config": { + "description": "Mobile app configuration (top-level; moved out of the config blob).", + "$ref": "#/components/schemas/MobileConfig" + }, "enabled": { "type": "boolean", "description": "Enable/Disable the portal access" @@ -16748,6 +19264,10 @@ "auto_redirect_to_sso": { "type": "boolean", "description": "Decide whether to automatically redirect to the provider page during login, which would completely bypass showing the portal authentication page." + }, + "prevent_user_enumeration": { + "type": "boolean", + "description": "Opt-in. When true, suppresses responses that reveal whether a user exists for public, pre-authentication actions (the login entry-point check and self-registration), at the expense of some UX. Already-authenticated actions are unaffected. Default false.\n" } } }, @@ -16845,6 +19365,13 @@ "type": "boolean", "example": true, "description": "Require symbols" + }, + "password_history_size": { + "type": "integer", + "minimum": 0, + "maximum": 24, + "example": 3, + "description": "Number of previous passwords a user is prevented from reusing. Set to 0 to disable reuse prevention. Maps to Cognito's PasswordHistorySize and requires the user pool to be on the Essentials or Plus feature plan." } } } @@ -17161,6 +19688,90 @@ "description": "ID of the portal", "example": "453ad7bf-86d5-46c8-8252-bcc868df5e3c" }, + "PortalDataExportColumn": { + "type": "object", + "description": "One column of the portal data export CSV.", + "required": [ + "key", + "header", + "source" + ], + "properties": { + "key": { + "type": "string", + "description": "Stable, immutable column id (snake_case). Append-only contract: never rename, reorder semantics, or remove an existing key; new columns are appended.\n", + "example": "vertragsnummer" + }, + "header": { + "type": "object", + "description": "Localized header label, keyed by language (de, en).", + "additionalProperties": { + "type": "string" + }, + "example": { + "de": "Vertragsnummer", + "en": "Contract number" + } + }, + "source": { + "type": "object", + "description": "Where the value comes from: relation hops from the anchor entity plus the attribute to read on the resolved entity. An empty path means the anchor itself.\n", + "required": [ + "attribute" + ], + "properties": { + "path": { + "type": "array", + "description": "Relation hops from the anchor entity (max 3).", + "items": { + "type": "string" + }, + "example": [ + "customer" + ] + }, + "attribute": { + "type": "string", + "description": "Attribute to read on the resolved entity.", + "example": "customer_number" + }, + "address_field": { + "type": "string", + "description": "Which part of an address-typed attribute to render.", + "enum": [ + "full", + "street", + "street_number", + "postal_code", + "city", + "additional_info", + "country" + ] + } + } + }, + "formatter": { + "type": "string", + "enum": [ + "text", + "date", + "money_cents", + "enum", + "address" + ] + }, + "enum_labels": { + "type": "object", + "description": "Localized value maps for the enum formatter, keyed by language then raw value.\n", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, "UpsertPortalConfigV3": { "allOf": [ { @@ -17275,10 +19886,8 @@ }, "SwappableConfig": { "type": "string", + "description": "Optional configuration item that a portal swap can additionally include. The swap always transfers the pages and the functional experience config that keep the portal working. These items are opt-in on top of that and are OFF by default. Domain and access/security settings (domain, cognito_details, auth_settings) can never be swapped and are therefore not part of this enum.", "enum": [ - "all", - "domain", - "users", "email_templates" ] }, diff --git a/packages/cli/definitions/dashboard.json b/packages/cli/definitions/dashboard.json index 464b92306..1009548f3 100644 --- a/packages/cli/definitions/dashboard.json +++ b/packages/cli/definitions/dashboard.json @@ -17,6 +17,9 @@ { "name": "Dashboards" }, + { + "name": "Insights" + }, { "name": "Visualisations" }, @@ -33,6 +36,53 @@ "tags": [ "Dashboards" ], + "parameters": [ + { + "$ref": "#/components/parameters/Search" + }, + { + "$ref": "#/components/parameters/CreatedBy" + }, + { + "$ref": "#/components/parameters/CreatedAfter" + }, + { + "$ref": "#/components/parameters/CreatedBefore" + }, + { + "$ref": "#/components/parameters/UpdatedAfter" + }, + { + "$ref": "#/components/parameters/UpdatedBefore" + }, + { + "$ref": "#/components/parameters/SharedWith" + }, + { + "$ref": "#/components/parameters/Owner" + }, + { + "$ref": "#/components/parameters/AccessibleTo" + }, + { + "$ref": "#/components/parameters/Favorite" + }, + { + "$ref": "#/components/parameters/FavoritesFirst" + }, + { + "$ref": "#/components/parameters/Sort" + }, + { + "$ref": "#/components/parameters/Order" + }, + { + "$ref": "#/components/parameters/Limit" + }, + { + "$ref": "#/components/parameters/Offset" + } + ], "responses": { "200": { "description": "List of dashboards available to the user", @@ -46,8 +96,15 @@ "items": { "$ref": "#/components/schemas/Dashboard" } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" } - } + }, + "required": [ + "results", + "pagination" + ] } } } @@ -85,6 +142,81 @@ } } }, + "/v1/dashboard/dashboards/favorites": { + "get": { + "operationId": "listFavoriteDashboardIds", + "summary": "listFavoriteDashboardIds", + "description": "Returns the current user's favorited dashboard ids, with no dashboard metadata. Lets a\nclient decide whether to default to a favorites-only view without first fetching the full\ndashboards list.\n", + "tags": [ + "Dashboards" + ], + "responses": { + "200": { + "description": "The current user's favorited dashboard ids", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "dashboard_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DashboardID" + } + }, + "total": { + "type": "integer" + } + }, + "required": [ + "dashboard_ids", + "total" + ] + } + } + } + } + } + } + }, + "/v1/dashboard/dashboards/{id}/favorite": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/DashboardID" + } + } + ], + "put": { + "operationId": "favoriteDashboard", + "summary": "favoriteDashboard", + "description": "Marks the dashboard as favorited by the current user. Idempotent — favoriting an\nalready-favorited dashboard is not an error. Requires only view-level access to the\ndashboard (unlike the edit-level check on `PATCH .../dashboards/{id}`).\n", + "tags": [ + "Dashboards" + ], + "responses": { + "204": { + "description": "The dashboard is now favorited by the current user" + } + } + }, + "delete": { + "operationId": "unfavoriteDashboard", + "summary": "unfavoriteDashboard", + "description": "Removes the current user's favorite for the dashboard. Idempotent — unfavoriting a\ndashboard that was not favorited is not an error, and neither is unfavoriting one that no\nlonger exists or is no longer shared with you. Requires no access to the dashboard, so that\na stale favorite always remains removable.\n", + "tags": [ + "Dashboards" + ], + "responses": { + "204": { + "description": "The dashboard is no longer favorited by the current user" + } + } + } + }, "/v1/dashboard/dashboards/{id}": { "parameters": [ { @@ -146,6 +278,36 @@ } } }, + "patch": { + "operationId": "patchDashboard", + "summary": "patchDashboard", + "description": "Partially update a dashboard by ID. Update content (title/tiles) and/or manage sharing\n(owners, shared_with, org_access) — only owners may change sharing.\n`owners` and `shared_with` replace the whole array (send the complete list); omit them to leave\nsharing unchanged, or send `shared_with: []` to revoke all per-user grants.\n", + "tags": [ + "Dashboards" + ], + "requestBody": { + "description": "The dashboard fields to update", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DashboardPatch" + } + } + } + }, + "responses": { + "200": { + "description": "The updated dashboard", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Dashboard" + } + } + } + } + } + }, "delete": { "operationId": "deleteDashboard", "summary": "deleteDashboard", @@ -167,6 +329,265 @@ } } }, + "/v1/dashboard/insights": { + "get": { + "operationId": "listInsights", + "summary": "listInsights", + "description": "List insights (saved charts) available to the user", + "tags": [ + "Insights" + ], + "parameters": [ + { + "$ref": "#/components/parameters/Search" + }, + { + "$ref": "#/components/parameters/VisualisationIdFilter" + }, + { + "$ref": "#/components/parameters/TagsFilter" + }, + { + "$ref": "#/components/parameters/TagsMatch" + }, + { + "$ref": "#/components/parameters/CreatedBy" + }, + { + "$ref": "#/components/parameters/CreatedAfter" + }, + { + "$ref": "#/components/parameters/CreatedBefore" + }, + { + "$ref": "#/components/parameters/UpdatedAfter" + }, + { + "$ref": "#/components/parameters/UpdatedBefore" + }, + { + "$ref": "#/components/parameters/SharedWith" + }, + { + "$ref": "#/components/parameters/Owner" + }, + { + "$ref": "#/components/parameters/AccessibleTo" + }, + { + "$ref": "#/components/parameters/Sort" + }, + { + "$ref": "#/components/parameters/Order" + }, + { + "$ref": "#/components/parameters/Limit" + }, + { + "$ref": "#/components/parameters/Offset" + } + ], + "responses": { + "200": { + "description": "List of insights available to the user", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Insight" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "required": [ + "results", + "pagination" + ] + } + } + } + } + } + }, + "post": { + "operationId": "createInsight", + "summary": "createInsight", + "description": "Create a new insight (saved chart). The caller becomes the owner.", + "tags": [ + "Insights" + ], + "requestBody": { + "description": "The insight to create", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Insight" + } + } + } + }, + "responses": { + "201": { + "description": "The created insight", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Insight" + } + } + } + } + } + } + }, + "/v1/dashboard/insights/tags": { + "get": { + "operationId": "listInsightTags", + "summary": "listInsightTags", + "description": "List the distinct tags used by insights in the organization (for filter facets)", + "tags": [ + "Insights" + ], + "responses": { + "200": { + "description": "Distinct tags used by insights in the organization", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/v1/dashboard/insights/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/InsightID" + } + } + ], + "get": { + "operationId": "getInsight", + "summary": "getInsight", + "description": "Get insight by ID", + "tags": [ + "Insights" + ], + "responses": { + "200": { + "description": "The insight with the passed ID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Insight" + } + } + } + } + } + }, + "put": { + "operationId": "putInsight", + "summary": "putInsight", + "description": "Replace an insight's content by ID. Sharing is managed via patchInsight.", + "tags": [ + "Insights" + ], + "requestBody": { + "description": "The updated insight", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Insight" + } + } + } + }, + "responses": { + "200": { + "description": "The insight with the passed ID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Insight" + } + } + } + } + } + }, + "patch": { + "operationId": "patchInsight", + "summary": "patchInsight", + "description": "Partially update an insight by ID. Update content (name/description/visualisation/tags) and/or\nmanage sharing (owners, shared_with, org_access) — only owners may change sharing.\n`owners` and `shared_with` replace the whole array (send the complete list); omit them to leave\nsharing unchanged, or send `shared_with: []` to revoke all per-user grants.\n", + "tags": [ + "Insights" + ], + "requestBody": { + "description": "The insight fields to update", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InsightPatch" + } + } + } + }, + "responses": { + "200": { + "description": "The updated insight", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Insight" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteInsight", + "summary": "deleteInsight", + "description": "Delete an insight by ID. Only owners may delete.", + "tags": [ + "Insights" + ], + "responses": { + "200": { + "description": "The deleted insight", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Insight" + } + } + } + } + } + } + }, "/v1/dashboard/visualisations": { "get": { "operationId": "listAvailableVisualisations", @@ -251,72 +672,313 @@ "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "Dashboard": { + "description": "A dashboard configuration. A dashboard is a canvas whose tiles arrange and\nposition insights (saved charts). Tiles may reference an insight by `insight_id`\nor, for backwards compatibility, embed a visualisation inline.\n", + "allOf": [ + { + "$ref": "#/components/schemas/AccessControl" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/DashboardID" + }, + "title": { + "type": "string", + "example": "Employee Dashboard" + }, + "tiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DashboardTile" + }, + "example": [ + { + "id": "e4af1297-1fd6-440f-9846-f475f580d40f", + "coordinates": {}, + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" + } + ] + }, + "favorited": { + "description": "Whether the requesting user has favorited this dashboard.", + "type": "boolean", + "readOnly": true, + "example": false + } + }, + "required": [ + "title", + "tiles" + ] + } + ] + }, + "DashboardTileID": { + "description": "Unique identifier for a tile in a dashboard", + "type": "string", + "format": "uuid", + "example": "e4af1297-1fd6-440f-9846-f475f580d40f" + }, + "SharePermission": { + "description": "Permission level granted to a user (or the whole organization) on a shared resource.\n`view` allows read-only access; `edit` additionally allows updating the content.\nFull control (delete and managing sharing) is reserved for owners.\n", + "type": "string", + "enum": [ + "view", + "edit" + ], + "example": "view" + }, + "ShareGrant": { + "description": "Grants a single user a permission level on a resource", "type": "object", - "description": "Adashboard configuration with tiles", "properties": { - "id": { - "$ref": "#/components/schemas/DashboardID" + "user_id": { + "description": "The id of the user the resource is shared with", + "type": "string", + "example": "10598" }, - "title": { + "permission": { + "$ref": "#/components/schemas/SharePermission" + } + }, + "required": [ + "user_id", + "permission" + ] + }, + "OrgAccess": { + "description": "Optional organization-wide grant. When set, every user in the resource's organization\nis granted this permission level. `null` (or omitted) means the resource is not shared\norg-wide.\n", + "anyOf": [ + { + "type": "string", + "enum": [ + "view", + "edit" + ] + }, + { + "enum": [ + null + ] + } + ], + "example": "view" + }, + "AccessControl": { + "description": "Ownership and sharing metadata common to dashboards and insights. `created_*`/`updated_*`\nfields are managed by the server. `owners`, `shared_with` and `org_access` describe who\nmay access the resource. New resources are private to their creator until shared.\n", + "type": "object", + "properties": { + "created_by": { + "description": "Id of the user who created the resource", + "type": "string", + "readOnly": true, + "example": "10598" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_by": { + "description": "Id of the user who last updated the resource", "type": "string", - "example": "Employee Dashboard" + "readOnly": true, + "example": "10598" }, - "tiles": { + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "owner_org_id": { + "description": "Id of the organisation that owns the resource. Set at creation time and immutable.", + "type": "string", + "readOnly": true, + "example": "739224" + }, + "owners": { + "description": "User ids with full control over the resource (view, edit, delete and manage sharing).\nThe creator is always an owner. There must always be at least one owner.\n", "type": "array", "items": { - "$ref": "#/components/schemas/DashboardTile" + "type": "string" }, "example": [ - { - "id": "e4af1297-1fd6-440f-9846-f475f580d40f", - "coordinates": {}, - "visualisation": "timechart", - "visualisation_config": { - "query": { - "dataset": "entity_operations", - "measure": "count_operations", - "filters": [ - { - "operation": [ - "createEntity" - ] - }, - { - "entity_schema": [ - "opportunity" - ] - } - ], - "dimensions": [ - { - "time_with_granularity": "month" - }, - { - "entity_attribute": "source.title" - } - ] - }, - "options": { - "type": "line" - } + "10598" + ] + }, + "shared_with": { + "description": "Per-user sharing grants", + "type": "array", + "items": { + "$ref": "#/components/schemas/ShareGrant" + } + }, + "org_access": { + "$ref": "#/components/schemas/OrgAccess" + } + } + }, + "AccessControlUpdate": { + "description": "Sharing fields that can be mutated via PATCH (owners only). `owners` and `shared_with` are\nfull replacements of their arrays, not merges — omit a field to leave it unchanged.\n", + "type": "object", + "properties": { + "owners": { + "description": "Replaces the entire list of owner user ids. Must keep at least one owner (else 400).\nOmit to leave owners unchanged.\n", + "type": "array", + "items": { + "type": "string" + } + }, + "shared_with": { + "description": "Replaces the entire list of per-user grants — any existing grant not included is removed.\nOmit to leave sharing unchanged; send an empty array to revoke all per-user grants.\n", + "type": "array", + "items": { + "$ref": "#/components/schemas/ShareGrant" + } + }, + "org_access": { + "$ref": "#/components/schemas/OrgAccess" + } + } + }, + "DashboardPatch": { + "description": "Fields that can be partially updated on a dashboard", + "allOf": [ + { + "$ref": "#/components/schemas/AccessControlUpdate" + }, + { + "type": "object", + "properties": { + "title": { + "type": "string", + "example": "Employee Dashboard" + }, + "tiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DashboardTile" } } - ] + } } - }, - "required": [ - "title", - "tiles" ] }, - "DashboardTileID": { - "description": "Unique identifier for a tile in a dashboard", + "InsightID": { + "description": "Unique identifier for an insight (a saved chart / visualisation)", "type": "string", "format": "uuid", - "example": "e4af1297-1fd6-440f-9846-f475f580d40f" + "example": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" + }, + "Insight": { + "description": "An insight is a saved, reusable chart definition (a visualisation plus its query/options).\nInsights exist independently of dashboards: they can be created, shared and edited on their\nown, and referenced by one or more dashboard tiles via `insight_id`.\n", + "allOf": [ + { + "$ref": "#/components/schemas/AccessControl" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/InsightID" + }, + "name": { + "description": "Human readable name of the insight", + "type": "string", + "example": "Opportunities created by journeys every month" + }, + "description": { + "type": "string", + "example": "Monthly count of opportunities grouped by source journey" + }, + "visualisation_id": { + "$ref": "#/components/schemas/VisualisationId" + }, + "visualisation_config": { + "$ref": "#/components/schemas/VisualisationConfig" + }, + "tags": { + "$ref": "#/components/schemas/Tags" + } + }, + "required": [ + "name", + "visualisation_id" + ] + } + ] + }, + "InsightPatch": { + "description": "Fields that can be partially updated on an insight", + "allOf": [ + { + "$ref": "#/components/schemas/AccessControlUpdate" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "visualisation_id": { + "$ref": "#/components/schemas/VisualisationId" + }, + "visualisation_config": { + "$ref": "#/components/schemas/VisualisationConfig" + }, + "tags": { + "$ref": "#/components/schemas/Tags" + } + } + } + ] + }, + "Tags": { + "description": "Free-form labels for grouping and filtering insights", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "revenue", + "marketing" + ] + }, + "Pagination": { + "description": "Offset-based pagination metadata for list responses", + "type": "object", + "properties": { + "total": { + "description": "Total number of results matching the query (ignoring limit/offset)", + "type": "integer" + }, + "limit": { + "description": "Page size used for this response", + "type": "integer" + }, + "offset": { + "description": "Number of results skipped before this page", + "type": "integer" + }, + "has_more": { + "description": "Whether more results exist beyond this page", + "type": "boolean" + } + }, + "required": [ + "total", + "limit", + "offset", + "has_more" + ] }, "DashboardTile": { "type": "object", + "description": "A positioned tile on a dashboard canvas. A tile references a saved insight via\n`insight_id`. Inline `visualisation_id`/`visualisation_config` remain supported for\nbackwards compatibility (ad-hoc tiles that are not backed by a shared insight).\n", "properties": { "id": { "$ref": "#/components/schemas/DashboardTileID" @@ -328,6 +990,10 @@ "type": "string", "example": "Number of opportunities created by journeys every month" }, + "insight_id": { + "description": "Reference to a saved insight rendered by this tile", + "$ref": "#/components/schemas/InsightID" + }, "visualisation_id": { "$ref": "#/components/schemas/VisualisationId" }, @@ -461,6 +1127,211 @@ } } }, + "parameters": { + "Search": { + "name": "q", + "in": "query", + "required": false, + "description": "Free-text search over name/title and description (case-insensitive substring).", + "schema": { + "type": "string" + } + }, + "VisualisationIdFilter": { + "name": "visualisation_id", + "in": "query", + "required": false, + "description": "Filter insights by one or more visualisation ids.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VisualisationId" + } + } + }, + "TagsFilter": { + "name": "tags", + "in": "query", + "required": false, + "description": "Filter by one or more tags (see tags_match for any/all semantics).", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "TagsMatch": { + "name": "tags_match", + "in": "query", + "required": false, + "description": "Whether a result must match any (default) or all of the given tags.", + "schema": { + "type": "string", + "enum": [ + "any", + "all" + ], + "default": "any" + } + }, + "CreatedBy": { + "name": "created_by", + "in": "query", + "required": false, + "description": "Filter by the id of the creating user.", + "schema": { + "type": "string" + } + }, + "CreatedAfter": { + "name": "created_after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + "CreatedBefore": { + "name": "created_before", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + "UpdatedAfter": { + "name": "updated_after", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + "UpdatedBefore": { + "name": "updated_before", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + "SharedWith": { + "name": "shared_with", + "in": "query", + "required": false, + "description": "Filter to resources shared with any of the given user ids.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Owner": { + "name": "owner", + "in": "query", + "required": false, + "description": "Filter to resources owned by any of the given user ids.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "AccessibleTo": { + "name": "accessible_to", + "in": "query", + "required": false, + "description": "Filter to resources the given user id may view (owner, shared, org-wide or legacy).", + "schema": { + "type": "string" + } + }, + "Favorite": { + "name": "favorite", + "in": "query", + "required": false, + "description": "Dashboards only. Filter to only (`true`) or exclude (`false`) the requesting user's\nfavorited dashboards. A user with zero favorites and `favorite=true` gets an empty page,\nnot the unfiltered list.\n", + "schema": { + "type": "boolean" + } + }, + "FavoritesFirst": { + "name": "favorites_first", + "in": "query", + "required": false, + "description": "Dashboards only. No filtering — sorts the requesting user's favorited dashboards first,\npreserving the existing secondary ordering (`sort`/`order`) otherwise.\n", + "schema": { + "type": "boolean" + } + }, + "Sort": { + "name": "sort", + "in": "query", + "required": false, + "description": "Field to sort by. `name` and `title` are aliases for the resource's display name and are normalised per resource: dashboards sort by `title` and insights by `name`, whichever of the two values is sent. String sorts are case-insensitive.", + "schema": { + "type": "string", + "enum": [ + "created_at", + "updated_at", + "name", + "title" + ], + "default": "updated_at" + } + }, + "Order": { + "name": "order", + "in": "query", + "required": false, + "description": "Sort direction. Defaults to ascending (preserving the pre-migration order).", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + "Limit": { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum results to return (max 200). Omit to return all matching results (pagination is opt-in).", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200 + } + }, + "Offset": { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip, for pagination. Use with limit (page N = offset N*limit).", + "schema": { + "type": "integer", + "minimum": 0, + "default": 0 + } + } + }, "securitySchemes": { "EpilotAuth": { "type": "http", diff --git a/packages/cli/definitions/deduplication.json b/packages/cli/definitions/deduplication.json index 469d61cec..56dcc1a3c 100644 --- a/packages/cli/definitions/deduplication.json +++ b/packages/cli/definitions/deduplication.json @@ -102,6 +102,222 @@ } } } + }, + "/v1/detect-duplicates": { + "post": { + "operationId": "detectDuplicates", + "summary": "detectDuplicates", + "description": "Detects potential duplicate entities for the given entity using the schema's prioritized uniqueness rules. Returns matches with a confidence score.", + "tags": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DetectDuplicatesRequestBody" + } + } + } + }, + "responses": { + "200": { + "description": "Potential duplicates ranked by rule priority", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DetectDuplicatesResponse" + } + } + } + } + } + } + }, + "/v1/duplicates/dismiss": { + "post": { + "operationId": "dismissDuplicates", + "summary": "dismissDuplicates", + "description": "Confirms entities as NOT duplicates: clears the internal duplicate-detection flags (_matching_entities) on each given entity, so they stop appearing as open duplicate sets. The records themselves are not modified otherwise.", + "tags": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DismissDuplicatesRequestBody" + } + } + } + }, + "responses": { + "200": { + "description": "Flags cleared", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DismissDuplicatesResponse" + } + } + } + } + } + } + }, + "/v1/uniqueness-criteria": { + "get": { + "operationId": "listUniquenessCriteria", + "summary": "listUniquenessCriteria", + "description": "Lists UniquenessCriteria for the requesting organization. Optionally filtered by schema.", + "tags": [], + "parameters": [ + { + "name": "schema", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter results to a specific entity schema." + } + ], + "responses": { + "200": { + "description": "Array of UniquenessCriteria records", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteriaListResponse" + } + } + } + } + } + }, + "post": { + "operationId": "createUniquenessCriteria", + "summary": "createUniquenessCriteria", + "description": "Creates a new UniquenessCriteria record.", + "tags": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteriaCreateBody" + } + } + } + }, + "responses": { + "201": { + "description": "Created record", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteria" + } + } + } + } + } + } + }, + "/v1/uniqueness-criteria/{schema}": { + "get": { + "operationId": "getUniquenessCriteria", + "summary": "getUniquenessCriteria", + "description": "Fetch a single UniquenessCriteria record.", + "tags": [], + "parameters": [ + { + "name": "schema", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Entity schema slug." + } + ], + "responses": { + "200": { + "description": "The criteria record", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteria" + } + } + } + }, + "404": { + "description": "Record not found" + } + } + }, + "put": { + "operationId": "updateUniquenessCriteria", + "summary": "updateUniquenessCriteria", + "description": "Replace the matchRules on an existing UniquenessCriteria record.", + "tags": [], + "parameters": [ + { + "name": "schema", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Entity schema slug." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteriaUpdateBody" + } + } + } + }, + "responses": { + "200": { + "description": "Updated record", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UniquenessCriteria" + } + } + } + }, + "404": { + "description": "Record not found" + } + } + }, + "delete": { + "operationId": "deleteUniquenessCriteria", + "summary": "deleteUniquenessCriteria", + "description": "Delete a UniquenessCriteria record.", + "tags": [], + "parameters": [ + { + "name": "schema", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Entity schema slug." + } + ], + "responses": { + "204": { + "description": "Record deleted" + }, + "404": { + "description": "Record not found" + } + } + } } }, "components": { @@ -120,26 +336,41 @@ "type": "object", "properties": { "toKeep": { - "type": "string" + "type": "string", + "minLength": 1, + "description": "Entity id to keep and merge the duplicates into" }, "toDelete": { "type": "array", "items": { - "type": "string" - } + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "description": "Non-empty list of duplicate entity ids to merge into toKeep and delete" } }, "required": [ "toKeep", "toDelete" ] - } + }, + "minItems": 1 }, "DeduplicateRequestResponse": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Entity" - } + "type": "object", + "properties": { + "deduplicatedEntities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + }, + "description": "The merged survivor entity of each processed set" + } + }, + "required": [ + "deduplicatedEntities" + ] }, "Entity": { "type": "object", @@ -303,6 +534,243 @@ "updatedAt" ], "description": "Represents an async deduplication job" + }, + "DetectDuplicatesRequestBody": { + "type": "object", + "properties": { + "schema": { + "type": "string", + "minLength": 1, + "description": "Entity schema to search (e.g. 'contact'). Must have UniquenessCriteria configured for the calling org." + }, + "entity": { + "type": "object", + "additionalProperties": {}, + "description": "The entity to look up potential duplicates for. Attribute values are extracted from this entity per the schema's match rules; system fields such as '_id' are supported. A rule is evaluated when at least one of its attributes resolves to a non-empty value on this entity; attributes without a value require candidates to also lack a value for them." + } + }, + "required": [ + "schema", + "entity" + ] + }, + "DetectDuplicatesResponse": { + "type": "object", + "properties": { + "matches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DetectedDuplicateMatch" + } + } + }, + "required": [ + "matches" + ] + }, + "DetectedDuplicateMatch": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score for the match, between 0 and 1." + }, + "matched_attributes": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "description": "Names of the attributes that matched on the rule that produced this hit." + } + }, + "required": [ + "entity", + "confidence", + "matched_attributes" + ] + }, + "DismissDuplicatesRequestBody": { + "type": "object", + "properties": { + "schema": { + "type": "string", + "minLength": 1 + }, + "entityIds": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 100, + "description": "Entities whose duplicate flags (_matching_entities) are cleared — the records are confirmed as NOT duplicates and stay separate" + } + }, + "required": [ + "schema", + "entityIds" + ] + }, + "DismissDuplicatesResponse": { + "type": "object", + "properties": { + "dismissed": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Entity ids whose duplicate flags were cleared" + } + }, + "required": [ + "dismissed" + ] + }, + "UniquenessCriteriaListResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UniquenessCriteria" + } + } + }, + "required": [ + "items" + ] + }, + "UniquenessCriteria": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the criteria record." + }, + "orgId": { + "type": "string", + "description": "Organization the criteria belong to." + }, + "schema": { + "type": "string", + "minLength": 1, + "description": "Entity schema these criteria apply to (e.g. 'contact')." + }, + "matchRules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchRule" + }, + "minItems": 1, + "description": "Ordered list of match rules. Order is the evaluation priority." + }, + "createdAt": { + "type": "string", + "description": "ISO 8601 timestamp of record creation.", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "ISO 8601 timestamp of last update.", + "format": "date-time" + } + }, + "required": [ + "id", + "orgId", + "schema", + "matchRules", + "createdAt", + "updatedAt" + ], + "description": "Defines what makes an entity of a given schema unique within an organization." + }, + "MatchRule": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100, + "description": "Optional human-readable label for the rule. Purely descriptive; not used during matching." + }, + "attributes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchAttribute" + }, + "minItems": 1, + "description": "Attributes that must all match for this rule to fire. Attributes without a value on the source entity must also have no value on a matching candidate." + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence assigned to matches produced by this rule, between 0 and 1." + } + }, + "required": [ + "attributes", + "confidence" + ], + "description": "One way to identify the same entity. Evaluated in order; first rule whose attributes are all available and that returns hits wins." + }, + "MatchAttribute": { + "type": "object", + "properties": { + "attribute": { + "type": "string", + "minLength": 1, + "description": "Name of the entity attribute to match on. Its query path is resolved from the entity schema at query time." + } + }, + "required": [ + "attribute" + ], + "description": "One attribute participating in a match rule. Wrapped as an object so per-attribute options can be added later." + }, + "UniquenessCriteriaCreateBody": { + "type": "object", + "properties": { + "schema": { + "type": "string", + "minLength": 1, + "description": "Entity schema these criteria apply to." + }, + "matchRules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchRule" + }, + "minItems": 1 + } + }, + "required": [ + "schema", + "matchRules" + ] + }, + "UniquenessCriteriaUpdateBody": { + "type": "object", + "properties": { + "matchRules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchRule" + }, + "minItems": 1 + } + }, + "required": [ + "matchRules" + ] } } }, diff --git a/packages/cli/definitions/design.json b/packages/cli/definitions/design.json index a09099d7b..2e4c25182 100644 --- a/packages/cli/definitions/design.json +++ b/packages/cli/definitions/design.json @@ -302,6 +302,71 @@ } } }, + "/v1/designs/{designId}/duplicate": { + "post": { + "operationId": "duplicateDesign", + "summary": "duplicateDesign", + "description": "Duplicate an existing design owned by the user organization. The copy carries over the style, custom CSS and name (marked as a copy) but does NOT carry over journey/portal relations, so the new design starts with 0 consumers, exactly like a freshly created design.", + "tags": [ + "design-builder" + ], + "parameters": [ + { + "in": "path", + "name": "designId", + "required": true, + "schema": { + "$ref": "#/components/schemas/DesignId" + } + } + ], + "responses": { + "201": { + "description": "Success - design duplicated with success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddDesignRes" + } + } + } + }, + "400": { + "description": "Validation Errors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "401": { + "description": "Authentication Errors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "404": { + "description": "No design found" + }, + "500": { + "description": "Other errors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + } + } + } + }, "/v1/designs/{designId}/parse": { "get": { "operationId": "getThemeFromDesign", diff --git a/packages/cli/definitions/email-settings.json b/packages/cli/definitions/email-settings.json index 850e1e28b..b043c78a6 100644 --- a/packages/cli/definitions/email-settings.json +++ b/packages/cli/definitions/email-settings.json @@ -26,6 +26,10 @@ "name": "O365 Outlook Connection", "description": "Microsoft Office 365 Outlook integration endpoints. These endpoints enable organizations to:\n- Initiate OAuth connections with Microsoft 365\n- Connect Outlook shared mailboxes\n- Manage connection status and tokens\n- Disconnect integrations when needed\n\nThe integration supports multiple Azure AD tenants and handles token refresh automatically.\n" }, + { + "name": "Custom SMTP", + "description": "Custom SMTP connection management. Lets organizations configure their own outbound\nSMTP server (e.g. Mailgun, SendGrid, a self-hosted Postfix) for sending emails\nfrom epilot via their existing provider. Credentials are encrypted at rest using\na customer-managed KMS key. Only outbound sending is supported — replies do not\ncome back into epilot.\n" + }, { "name": "Channels", "description": "Channel integration endpoints. Channels represent external communication providers\n(e.g., Microsoft Teams, WhatsApp) that can be connected to an epilot organization.\n\nAll channel endpoints live under `/v2/channels/{channel}`.\n" @@ -610,6 +614,81 @@ } } }, + "/v2/outlook/calendar/admin-consent-status": { + "get": { + "operationId": "getCalendarAdminConsentStatus", + "summary": "getCalendarAdminConsentStatus", + "description": "Reports whether the caller's organization can connect personal Outlook\ncalendars.\n\nConnecting a personal calendar requires the tenant admin to have\nconsented to epilot's Microsoft 365 app. That consent is tenant-wide,\none-time, and granted for the full scope set (mail + calendar), so the\norg-level Outlook connection set up during onboarding is the gate.\n\nWhen consent is missing the response includes `admin_consent_url` for\nthe admin to open, and the UI should ask the user to involve their IT\ninstead of starting an OAuth flow that would fail.\n", + "tags": [ + "O365 Outlook Connection" + ], + "responses": { + "200": { + "description": "Admin consent status for the organization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarAdminConsentStatus" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/v2/outlook/calendar/me": { + "get": { + "operationId": "getMyCalendarConnection", + "summary": "getMyCalendarConnection", + "description": "Returns the calling user's personal Outlook calendar connection,\nor null when the user hasn't connected yet.\n\nPer-user calendar connections are distinct from the org-level\nshared-mailbox connection (`/v2/outlook/connection/status`): each\nuser connects their own delegated token because reading user A's\ncalendar requires user A's consent.\n", + "tags": [ + "O365 Outlook Connection" + ], + "responses": { + "200": { + "description": "Calendar connection (or null if absent)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyCalendarConnectionResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + }, + "delete": { + "operationId": "disconnectMyCalendar", + "summary": "disconnectMyCalendar", + "description": "Removes the calling user's personal calendar connection.", + "tags": [ + "O365 Outlook Connection" + ], + "responses": { + "204": { + "description": "Disconnected" + }, + "404": { + "description": "No connection to disconnect" + }, + "500": { + "description": "Internal server error" + } + } + } + }, "/v2/outlook/connection/disconnect": { "post": { "operationId": "disconnectOutlook", @@ -817,6 +896,28 @@ "description": "Shared inbox ID to associate with the mailbox. Defaults to the default shared inbox.", "default": "default" }, + "name": { + "type": "string", + "description": "Sender name to use for the Outlook mailbox email address." + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "User IDs for which this email address should be available by default." + }, + "group_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Group IDs for which this email address should be available by default." + }, + "default_signature_id": { + "type": "string", + "description": "Default signature ID to use for this Outlook mailbox email address." + }, "mailboxSyncTimeframe": { "type": "string", "enum": [ @@ -973,7 +1074,11 @@ ], "properties": { "timeframe": { - "$ref": "#/components/schemas/MailboxSyncTimeframePeriods", + "allOf": [ + { + "$ref": "#/components/schemas/MailboxSyncTimeframePeriods" + } + ], "description": "Sync period:\n- 5m: last 5 minutes (quick sync)\n- 1w: 1 week\n- 2w: 2 weeks\n- 1m: 1 month\n" } } @@ -1160,40 +1265,452 @@ "summary": "getConnectedOutlookEmails", "description": "Returns all Outlook email addresses connected to the organization.\n", "tags": [ - "O365 Outlook Connection" + "O365 Outlook Connection" + ], + "responses": { + "200": { + "description": "Outlook emails retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "outlook_emails", + "count" + ], + "properties": { + "outlook_emails": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConnectedOutlookEmail" + } + }, + "count": { + "type": "integer", + "description": "Number of Outlook emails" + } + } + } + } + } + }, + "400": { + "description": "Bad request - missing organization ID" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/v2/smtp/connections": { + "get": { + "operationId": "listSmtpConnections", + "summary": "listSmtpConnections", + "description": "Returns all custom SMTP connections configured for the organization.\nPasswords are never returned.\n", + "tags": [ + "Custom SMTP" + ], + "responses": { + "200": { + "description": "SMTP connections listed", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "connections", + "has_connections" + ], + "properties": { + "connections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SmtpConnection" + } + }, + "has_connections": { + "type": "boolean" + } + } + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "post": { + "operationId": "createSmtpConnection", + "summary": "createSmtpConnection", + "description": "Creates a new custom SMTP connection. Runs a live verify against the SMTP server\nbefore persisting; on failure the request is rejected and nothing is saved. Pass\n`skip_test=true` to store the configuration untested instead.\n", + "tags": [ + "Custom SMTP" + ], + "parameters": [ + { + "name": "skip_test", + "in": "query", + "required": false, + "description": "Save without running the live verify. Use when the server is not reachable from\nepilot yet, or when a referenced `{{ env.* }}` secret has not been set. The\nconnection is stored untested — `last_test_status` is left unset rather than\nclaimed as `ok` — and env references are not resolved either, since an\nunresolvable one is expected in this case.\n", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSmtpConnectionRequest" + } + } + } + }, + "responses": { + "201": { + "description": "SMTP connection created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConnection" + } + } + } + }, + "400": { + "description": "Validation error, SMTP test failed, or refused for security reasons\n(private IP, plaintext on non-localhost host, invalid TLS).\n" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/smtp/connections/{connectionId}": { + "parameters": [ + { + "name": "connectionId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "get": { + "operationId": "getSmtpConnection", + "summary": "getSmtpConnection", + "description": "Returns a single custom SMTP connection by id. The password is never returned.", + "tags": [ + "Custom SMTP" + ], + "responses": { + "200": { + "description": "SMTP connection retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConnection" + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "operationId": "updateSmtpConnection", + "summary": "updateSmtpConnection", + "description": "Partial update; omitted fields keep their existing values. The merged\nconfiguration is verified against the SMTP server before persisting. Pass\n`skip_test=true` to store it untested instead, which also clears any previous\ntest result — it described a configuration that no longer applies.\n", + "tags": [ + "Custom SMTP" + ], + "parameters": [ + { + "name": "skip_test", + "in": "query", + "required": false, + "description": "Save without running the live verify. Use when the server is not reachable from\nepilot yet, or when a referenced `{{ env.* }}` secret has not been set. The\nconnection is stored untested — `last_test_status` is left unset rather than\nclaimed as `ok` — and env references are not resolved either, since an\nunresolvable one is expected in this case.\n", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSmtpConnectionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "SMTP connection updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConnection" + } + } + } + }, + "400": { + "description": "Validation error or SMTP test failed" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + }, + "delete": { + "operationId": "deleteSmtpConnection", + "summary": "deleteSmtpConnection", + "description": "Deletes a custom SMTP connection. Messages already sent are unaffected.", + "tags": [ + "Custom SMTP" + ], + "responses": { + "200": { + "description": "SMTP connection deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "connection_id": { + "type": "string" + } + } + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/v2/smtp/connections/{connectionId}/test": { + "parameters": [ + { + "name": "connectionId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "operationId": "testSmtpConnection", + "summary": "testSmtpConnection", + "description": "Re-runs a live SMTP verify against the saved configuration (EHLO + AUTH + NOOP + QUIT)\nand updates `last_test_status` / `last_tested_at` on the connection.\n", + "tags": [ + "Custom SMTP" + ], + "responses": { + "200": { + "description": "SMTP test executed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpTestResult" + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/v2/smtp/senders": { + "get": { + "operationId": "listSmtpSenders", + "summary": "listSmtpSenders", + "description": "Returns every address registered to send through a custom SMTP connection.", + "tags": [ + "Custom SMTP" + ], + "responses": { + "200": { + "description": "SMTP senders listed", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "senders" + ], + "properties": { + "senders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SmtpSender" + } + } + } + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "post": { + "operationId": "connectSmtpSender", + "summary": "connectSmtpSender", + "description": "Registers an address as a sender on a custom SMTP connection:\n 1. Creates the email address so it can be picked in the composer\n 2. Binds it to the connection, so outgoing mail from it is routed there\nThe SMTP server still has the final say on which From addresses it accepts.\n", + "tags": [ + "Custom SMTP" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConnectSmtpSenderRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Sender connected", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email_address", + "sender" + ], + "properties": { + "email_address": { + "$ref": "#/components/schemas/EmailAddressResponse" + }, + "sender": { + "$ref": "#/components/schemas/SmtpSender" + } + } + } + } + } + }, + "400": { + "description": "Validation error" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "description": "The referenced SMTP connection does not exist" + }, + "409": { + "description": "The address is already registered, here or in another organization" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/smtp/senders/{email}": { + "parameters": [ + { + "name": "email", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "email" + } + } + ], + "delete": { + "operationId": "disconnectSmtpSender", + "summary": "disconnectSmtpSender", + "description": "Removes a sender address: deletes the email address and its binding to the SMTP\nconnection. The connection itself and messages already sent are unaffected.\n", + "tags": [ + "Custom SMTP" ], "responses": { "200": { - "description": "Outlook emails retrieved successfully", + "description": "Sender disconnected", "content": { "application/json": { "schema": { "type": "object", "required": [ - "outlook_emails", - "count" + "success", + "email" ], "properties": { - "outlook_emails": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ConnectedOutlookEmail" - } + "success": { + "type": "boolean" }, - "count": { - "type": "integer", - "description": "Number of Outlook emails" + "email": { + "type": "string", + "format": "email" } } } } } }, - "400": { - "description": "Bad request - missing organization ID" + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" }, "500": { - "description": "Internal server error" + "$ref": "#/components/responses/InternalServerError" } } } @@ -1287,6 +1804,14 @@ "schema": { "type": "string" } + }, + { + "name": "clientdata", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -2078,6 +2603,12 @@ "calendar": { "type": "boolean", "description": "Request 'calendar' specific scopes." + }, + "return_to": { + "type": "string", + "maxLength": 512, + "pattern": "^/[^/\\\\].*$", + "description": "Optional same-origin frontend path to redirect to after the OAuth\nround-trip. Must start with a single \"/\" — protocol-relative\nand absolute URLs are rejected.\n" } } }, @@ -2644,6 +3175,89 @@ } } }, + "CalendarAdminConsentStatus": { + "type": "object", + "required": [ + "admin_consented" + ], + "properties": { + "admin_consented": { + "type": "boolean", + "description": "True when the tenant admin has consented to epilot's Microsoft 365\napp, so users in this organization can connect personal calendars.\n" + }, + "admin_consent_url": { + "type": "string", + "format": "uri", + "description": "Microsoft admin-consent URL for an administrator to open. Present\nonly when `admin_consented` is false.\n" + } + } + }, + "MyCalendarConnectionResponse": { + "type": "object", + "description": "Per-user calendar connection metadata, or null when the caller has\nnot connected their personal calendar yet.\n", + "properties": { + "connection": { + "allOf": [ + { + "$ref": "#/components/schemas/CalendarConnectionInfo" + } + ], + "nullable": true + } + } + }, + "CalendarConnectionInfo": { + "type": "object", + "required": [ + "user_id", + "tenant_id", + "status" + ], + "properties": { + "user_id": { + "type": "string", + "description": "Azure AD Object ID of the connected user" + }, + "tenant_id": { + "type": "string", + "description": "Microsoft Azure AD tenant ID" + }, + "status": { + "type": "string", + "enum": [ + "connected", + "expired" + ], + "description": "Token health for this calendar connection" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Granted permission scopes on the user's token" + }, + "connected_by_display_name": { + "type": "string" + }, + "connected_by_email": { + "type": "string", + "format": "email" + }, + "connected_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time" + } + } + }, "OutlookConnectionStatus": { "type": "object", "required": [ @@ -2726,6 +3340,269 @@ } } }, + "SmtpConnection": { + "type": "object", + "required": [ + "connection_id", + "smtp_host", + "smtp_port", + "smtp_secure", + "smtp_username" + ], + "properties": { + "connection_id": { + "type": "string", + "description": "Stable identifier for this SMTP connection" + }, + "smtp_host": { + "type": "string", + "description": "Hostname of the SMTP server (e.g. smtp.mailgun.org)" + }, + "smtp_port": { + "type": "string", + "description": "SMTP port (typically 587 for STARTTLS or 465 for TLS). A string, because it\nmay hold a `{{ env.your_key }}` reference rather than a literal port.\n" + }, + "smtp_secure": { + "type": "string", + "enum": [ + "tls", + "starttls" + ], + "description": "Connection security mode. Plaintext SMTP is not offered: it would put the\nSMTP password on the wire in the clear.\n- tls: Implicit TLS (port 465)\n- starttls: STARTTLS upgrade (port 587)\n" + }, + "smtp_username": { + "type": "string", + "description": "SMTP auth username" + }, + "smtp_password": { + "type": "string", + "description": "Only returned when the stored value is exactly one `{{ env.your_key }}`\nreference, which names a secret rather than being one — that lets the\nsettings UI edit the reference without dropping it. A literal password, or a\npartly templated one, is omitted entirely: submit an update without this\nfield to keep the stored value.\n" + }, + "connected_by_display_name": { + "type": "string" + }, + "connected_by_email": { + "type": "string", + "format": "email" + }, + "connected_by_user_id": { + "type": "string" + }, + "connected_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "last_tested_at": { + "type": "string", + "format": "date-time" + }, + "last_test_status": { + "type": "string", + "enum": [ + "ok", + "auth_failed", + "tls_failed", + "host_unreachable", + "timeout", + "secret_missing", + "unknown" + ] + }, + "last_test_error": { + "type": "string", + "description": "Latest test error message, if any" + } + } + }, + "CreateSmtpConnectionRequest": { + "type": "object", + "required": [ + "smtp_host", + "smtp_port", + "smtp_secure", + "smtp_username", + "smtp_password" + ], + "properties": { + "smtp_host": { + "type": "string" + }, + "smtp_port": { + "description": "A port between 1 and 65535, or a `{{ env.your_key }}` template resolving to\none. An integer is still accepted, so callers predating the template support\nkeep working.\n", + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": 1, + "maximum": 65535 + } + ] + }, + "smtp_secure": { + "type": "string", + "enum": [ + "tls", + "starttls" + ] + }, + "smtp_username": { + "type": "string" + }, + "smtp_password": { + "type": "string", + "description": "May contain `{{ env.your_key }}` templates referencing the organization's\nenvironment variables (including secrets), resolved at send time. Stored as\nentered, so the settings UI edits the template rather than a resolved value.\n", + "example": "{{ env.smtp_password }}" + } + } + }, + "UpdateSmtpConnectionRequest": { + "type": "object", + "description": "Partial update; omitted fields keep their existing values.", + "properties": { + "smtp_host": { + "type": "string" + }, + "smtp_port": { + "description": "A port between 1 and 65535, or a `{{ env.your_key }}` template resolving to\none. An integer is still accepted, so callers predating the template support\nkeep working.\n", + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": 1, + "maximum": 65535 + } + ] + }, + "smtp_secure": { + "type": "string", + "enum": [ + "tls", + "starttls" + ] + }, + "smtp_username": { + "type": "string" + }, + "smtp_password": { + "type": "string" + } + } + }, + "SmtpSender": { + "type": "object", + "description": "An address allowed to send through a custom SMTP connection. Mirrors an Outlook\nmailbox: the address, its display name and its assignees live on the shared\nemail-address record, while this row binds it to a set of SMTP credentials.\n", + "required": [ + "email", + "connection_id" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The sender address. Stored lower-cased." + }, + "connection_id": { + "type": "string", + "description": "SMTP connection this address sends through" + }, + "reply_to_email": { + "type": "string", + "description": "Default Reply-To for messages from this address. Custom SMTP is send-only, so\npoint this at a mailbox someone actually monitors. May contain a\n`{{ env.your_key }}` template, so this is not constrained to `format: email`.\nA per-message Reply-To still wins.\n" + }, + "connected_at": { + "type": "string", + "format": "date-time" + }, + "connected_by_user_id": { + "type": "string" + } + } + }, + "ConnectSmtpSenderRequest": { + "type": "object", + "required": [ + "email", + "connection_id" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Address to send from. Must be one the SMTP server accepts as a From." + }, + "connection_id": { + "type": "string", + "description": "SMTP connection to send through" + }, + "name": { + "type": "string", + "description": "Sender name shown on outgoing messages." + }, + "reply_to_email": { + "type": "string", + "description": "Email address, or a `{{ env.your_key }}` template resolving to one." + }, + "shared_inbox_id": { + "type": "string", + "description": "Shared inbox to associate with the address. Defaults to the default shared inbox.", + "default": "default" + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "User IDs for which this address should be available by default." + }, + "group_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Group IDs for which this address should be available by default." + }, + "default_signature_id": { + "type": "string", + "description": "Default signature ID to use for this address." + } + } + }, + "SmtpTestResult": { + "type": "object", + "required": [ + "status", + "tested_at" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ok", + "auth_failed", + "tls_failed", + "host_unreachable", + "timeout", + "secret_missing", + "unknown" + ] + }, + "error": { + "type": "string" + }, + "tested_at": { + "type": "string", + "format": "date-time" + } + } + }, "SignatureSetting": { "type": "string", "description": "Setting that allows to add a signature.", diff --git a/packages/cli/definitions/entity-mapping.json b/packages/cli/definitions/entity-mapping.json index 3f96c23c5..1da28b92b 100644 --- a/packages/cli/definitions/entity-mapping.json +++ b/packages/cli/definitions/entity-mapping.json @@ -763,6 +763,10 @@ "$ref": "#/components/schemas/EntityRef", "description": "A reference (id and schema) to the entity to be used as source." }, + "loop_ref": { + "$ref": "#/components/schemas/EntityRef", + "description": "Reference to the current iteration's entity when running inside a flow loop.\nWhen set, its fields and relations are merged into the source context;\non schema conflicts the loop entity wins, so paths like `._id` resolve\nto the iteration entity rather than to a same-schema relation of the source.\n" + }, "targets": { "type": "array", "items": { @@ -960,6 +964,11 @@ "type": "string" } }, + "use_uniqueness_criteria": { + "type": "boolean", + "default": false, + "description": "Execution wire flag set per automation by automation-workers: resolve the target entity via the organization's global uniqueness criteria (deduplication-api) instead of target_unique. Never persisted in stored mapping configs.\n" + }, "loop_config": { "type": "object", "description": "contains config in case of running in loop mode", @@ -970,8 +979,7 @@ }, "length": { "type": "number", - "description": "a hard limit of how many times the loop is allowed to run.", - "default": "the length of the array" + "description": "a hard limit of how many times the loop is allowed to run." } } }, @@ -1023,6 +1031,13 @@ "items": { "type": "string" } + }, + "graph_context": { + "type": "array", + "description": "Multi-hop entity graph lookups resolved before any mapping_attribute is evaluated. Each\nentry queries entity-api's `POST /v1/entity:graph` and merges every node's result into\nsourceContext under its own `graph.nodes[].id`, so mapping_attributes can `_copy`/`_template`\nfrom `.`. A graph node's value overwrites any existing sourceContext key of\nthe same name (a source entity field, a 1-hop relation, or another graph node).\n", + "items": { + "$ref": "#/components/schemas/GraphContextEntry" + } } }, "required": [ @@ -1093,6 +1108,13 @@ "description": "Include all relation tags (labels) present on the main entity relation", "default": false }, + "target_entity_tags": { + "type": "array", + "description": "Tags to add to the matched target entity's _tags array during mapping. Useful for assigning file collections to file entities.", + "items": { + "type": "string" + } + }, "override_with_source_filter": { "type": "boolean", "description": "Whether to override the relation source_filter with the specified one", @@ -1152,6 +1174,157 @@ "mode" ] }, + "GraphContextEntry": { + "type": "object", + "description": "One multi-hop graph lookup against entity-api's `POST /v1/entity:graph`, resolved during\ngraph_context enrichment (before mapping_attributes are evaluated). Every node in `graph.nodes`\nis merged into sourceContext under its own `id`, so listing several nodes here costs one\nentity-api call, not one per node.\n\nIf a node's `cardinality` is \"one\" (or it is the seed node), exactly one entity must be found\nfor it: zero or multiple matches fail the mapping execution instead of silently mapping\nmissing/wrong data. If \"many\" (the default), it resolves to an array - possibly empty - with no\nsuch failure. Each node's cardinality is validated independently.\n\n`seed.entity_id` and any `graph.nodes[].filter[].value` may contain `{{handlebars}}` placeholders\n(e.g. `{{contract._id}}`, `{{contract.origin_order}}`), resolved against the in-progress\nsourceContext (the source entity, its 1-hop relations, and any custom variables already\nresolved) before the graph query is sent.\n", + "properties": { + "seed": { + "$ref": "#/components/schemas/GraphSeed" + }, + "graph": { + "$ref": "#/components/schemas/GraphDefinition" + } + }, + "required": [ + "seed", + "graph" + ] + }, + "GraphSeed": { + "description": "Mirrors entity-api's GraphSeed (see entity-api openapi.yml) - the entity the graph traversal starts from.", + "type": "object", + "properties": { + "entity_id": { + "type": "string", + "description": "The id of the seed entity. Supports `{{handlebars}}` placeholders resolved against sourceContext.", + "example": "{{contract._id}}" + }, + "node_id": { + "type": "string", + "description": "The node ID in `graph.nodes` that corresponds to the seed entity.", + "example": "contact" + } + }, + "required": [ + "entity_id", + "node_id" + ] + }, + "GraphDefinition": { + "description": "Mirrors entity-api's GraphDefinition (see entity-api openapi.yml) - the shape of the graph to traverse.", + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphNode" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphEdge" + } + } + }, + "required": [ + "nodes", + "edges" + ] + }, + "GraphNode": { + "description": "Mirrors entity-api's GraphNode (see entity-api openapi.yml).", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this node in the graph definition.", + "example": "contact" + }, + "schema": { + "type": "string", + "description": "Entity schema slug for this node.", + "example": "contact" + }, + "cardinality": { + "type": "string", + "enum": [ + "one", + "many" + ], + "description": "\"one\": this node resolves to a single entity. \"many\" (default if unset): this node\nresolves to an array of entities.\n" + }, + "fields": { + "type": "array", + "description": "Optional entity fields to include in the hydrated response for this node.", + "items": { + "type": "string" + } + }, + "filter": { + "type": "array", + "description": "Narrows this node's traversal results to entities matching every filter (AND semantics).\nUseful for disambiguating among multiple entities reachable via the same graph edge.\n", + "items": { + "$ref": "#/components/schemas/GraphNodeFilter" + } + } + }, + "required": [ + "id", + "schema" + ] + }, + "GraphNodeFilter": { + "description": "Mirrors entity-api's GraphNodeFilter (see entity-api openapi.yml).", + "type": "object", + "properties": { + "attribute": { + "type": "string", + "description": "Entity attribute name to match against.", + "example": "order_number" + }, + "value": { + "description": "Literal value the attribute must exactly equal. Supports `{{handlebars}}` placeholders\nresolved against sourceContext when given as a string.\n", + "oneOf": [ + { + "type": "string", + "nullable": true + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ], + "example": "{{contract.origin_order}}" + } + }, + "required": [ + "attribute", + "value" + ] + }, + "GraphEdge": { + "description": "Mirrors entity-api's GraphEdge (see entity-api openapi.yml).", + "type": "object", + "properties": { + "from": { + "type": "string", + "description": "Source node ID.", + "example": "contact" + }, + "to": { + "type": "string", + "description": "Target node ID.", + "example": "order" + } + }, + "required": [ + "from", + "to" + ] + }, "MappingAttributeV2": { "type": "object", "properties": { @@ -1256,6 +1429,20 @@ "_random": { "description": "Generate random ids / numbers", "$ref": "#/components/schemas/RandomOperation" + }, + "_each": { + "description": "Iterate over a source array. Use with _as and _map.\nThe value is a path to resolve from the source entity context.\nExample: \"submission.meterReadings\"\n", + "type": "string", + "example": "submission.meterReadings" + }, + "_as": { + "description": "Name for the current iteration item in _each.\nAccessed as $ in _copy paths within _map.\nExample: \"reading\" (accessed as $reading)\n", + "type": "string", + "example": "reading" + }, + "_map": { + "description": "Operation to evaluate per _each iteration item.\nCan be any OperationNode, typically an object with _copy\nreferences to the $<_as name> alias.\n", + "$ref": "#/components/schemas/OperationNode" } }, "additionalProperties": true diff --git a/packages/cli/definitions/entity.json b/packages/cli/definitions/entity.json index d5ad201ec..e0b324aa8 100644 --- a/packages/cli/definitions/entity.json +++ b/packages/cli/definitions/entity.json @@ -2,7 +2,7 @@ "openapi": "3.0.2", "info": { "title": "Entity API", - "version": "2.9.0", + "version": "2.10.0", "description": "Flexible data layer for epilot Entities.\n\nUse this API configure and access your business objects like Contacts, Opportunities and Products.\n\n[Feature Documentation](https://docs.epilot.io/docs/entities/flexible-entities)\n" }, "tags": [ @@ -75,7 +75,8 @@ { "in": "query", "name": "latest", - "description": "When true, return the latest version instead of the frozen version for frozen schemas.", + "deprecated": true, + "description": "No longer has any effect: schema freezing is retired and the latest version is always returned.\n", "schema": { "type": "boolean", "default": false @@ -161,7 +162,8 @@ { "in": "query", "name": "latest", - "description": "When true, return the latest version instead of the frozen version for frozen schemas.", + "deprecated": true, + "description": "No longer has any effect: schema freezing is retired and the latest version is always returned.\n", "schema": { "type": "boolean", "default": false @@ -223,7 +225,7 @@ "get": { "operationId": "getSchema", "summary": "getSchema", - "description": "By default gets the current version of the Schema (frozen version if frozen, otherwise latest).\nPass ?latest=true to get the latest version when the schema is frozen.\nPass ?id= to get a specific version by ID.\n", + "description": "Gets the latest version of the Schema.\nPass ?id= to get a specific version by ID.\n", "tags": [ "Schemas" ], @@ -241,7 +243,8 @@ { "in": "query", "name": "latest", - "description": "When true, return the latest version instead of the frozen version for frozen schemas.", + "deprecated": true, + "description": "No longer has any effect: schema freezing is retired and the latest version is always returned.\n", "schema": { "type": "boolean", "default": false @@ -267,7 +270,7 @@ "put": { "operationId": "putSchema", "summary": "putSchema", - "description": "Create or update a schema with a new version.\nWhen the schema is frozen, writes update the latest version without affecting the frozen version.\n", + "description": "Create or update a schema with a new version.\n", "tags": [ "Schemas" ], @@ -792,7 +795,8 @@ "type": "boolean" }, "frozen_version": { - "description": "The version ID that is currently frozen, if any", + "deprecated": true, + "description": "No longer returned: schema freezing is retired.\n", "allOf": [ { "$ref": "#/components/schemas/SchemaId" @@ -815,7 +819,8 @@ "post": { "operationId": "freezeSchema", "summary": "freezeSchema", - "description": "Freeze a schema at its current version, or at a specific version.\nWhen frozen, getSchema returns the frozen version by default.\nNew edits via putSchema update the latest version without affecting the frozen version.\n", + "deprecated": true, + "description": "Deprecated no-op: schema freezing is retired and every read returns the latest version.\nReturns the latest version of the schema unchanged. This endpoint will be removed.\n", "tags": [ "Schemas" ], @@ -864,7 +869,8 @@ "post": { "operationId": "unfreezeSchema", "summary": "unfreezeSchema", - "description": "Unfreeze a schema. Promotes the latest version to the current version for all users.\n", + "deprecated": true, + "description": "Deprecated: schema freezing is retired. Cleans up a leftover frozen pointer by promoting the\nlatest version to the current version, and succeeds when there is nothing to clean up.\nThis endpoint will be removed.\n", "tags": [ "Schemas" ], @@ -1419,6 +1425,9 @@ { "$ref": "#/components/parameters/HydrateEntitiesQueryParam" }, + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "in": "query", "name": "fields", @@ -1568,6 +1577,9 @@ }, { "$ref": "#/components/parameters/HydrateEntitiesQueryParam" + }, + { + "$ref": "#/components/parameters/AnonymizeQueryParam" } ], "responses": { @@ -1776,6 +1788,9 @@ "Entities" ], "parameters": [ + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "name": "input", "in": "query", @@ -1944,6 +1959,9 @@ "Activity" ], "parameters": [ + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "$ref": "#/components/parameters/ActivityIdPathParam" }, @@ -2140,6 +2158,9 @@ "Entities" ], "parameters": [ + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "$ref": "#/components/parameters/EntitySlugPathParam" }, @@ -2173,6 +2194,9 @@ "Activity" ], "parameters": [ + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "$ref": "#/components/parameters/EntitySlugPathParam" }, @@ -2325,6 +2349,9 @@ { "$ref": "#/components/parameters/HydrateEntitiesQueryParam" }, + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "$ref": "#/components/parameters/IncludeReverseQueryParam" }, @@ -2551,6 +2578,9 @@ { "$ref": "#/components/parameters/HydrateEntitiesQueryParam" }, + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "in": "query", "name": "query", @@ -2620,6 +2650,9 @@ { "$ref": "#/components/parameters/HydrateEntitiesQueryParam" }, + { + "$ref": "#/components/parameters/AnonymizeQueryParam" + }, { "$ref": "#/components/parameters/IncludeReverseDeprecatedQueryParam" }, @@ -2940,6 +2973,69 @@ } } }, + "/v1/entity:abortImport": { + "post": { + "operationId": "abortEntityImport", + "summary": "Abort a running entity import", + "description": "Asks a running import to stop. Rows already imported are **kept** - this is a stop, not a\nrollback - and the job still produces its result reports for everything that landed before\nthe stop, then reaches the terminal status `CANCELLED`.\n\nThe stop is cooperative: the worker notices the request at its next batch boundary, so the\njob typically reaches `CANCELLED` within seconds. Poll `POST /v1/entity:import` with the\n`job_id` as usual to observe it.\n\nIdempotent - aborting an already-aborted or already-finished job is not an error.\n", + "tags": [ + "Import-Export" + ], + "parameters": [ + { + "in": "query", + "name": "job_id", + "required": true, + "description": "The ID of the import job to stop.", + "schema": { + "$ref": "#/components/schemas/ExportJobId" + } + }, + { + "in": "query", + "name": "schema", + "required": true, + "description": "Slug of the schema being imported, used to authorize the caller.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "202": { + "description": "The stop was requested. The job is still running until the worker picks the request up.\n", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "The job status at the time of the request." + }, + "jobPollId": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "No such import job" + }, + "429": { + "$ref": "#/components/responses/TooManyRequestsError" + } + } + } + }, "/v1/entity/views": { "get": { "operationId": "listSavedViews", @@ -2990,6 +3086,15 @@ "maximum": 250 } }, + { + "in": "query", + "name": "q", + "description": "Free-text search on the saved view name (case-insensitive)", + "required": false, + "schema": { + "type": "string" + } + }, { "in": "query", "name": "fields", @@ -3158,7 +3263,10 @@ ], "responses": { "200": { - "description": "List of all available saved views" + "description": "The saved view was deleted" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" }, "429": { "$ref": "#/components/responses/TooManyRequestsError" @@ -4967,6 +5075,63 @@ "example": true } } + }, + "grid_layout": { + "type": "object", + "deprecated": true, + "description": "Superseded by `widget_widths`. A whole-grid layout preset that assigned each widget a width by its POSITION, so a widget that rendered nothing shifted every width after it. Neither the entity app nor the entity builder reads it any more; values stored before it was replaced are left in place rather than migrated.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the chosen grid-layout preset." + }, + "columns": { + "type": "number", + "description": "Number of columns in the widget grid." + }, + "cells": { + "type": "array", + "description": "Per-cell column span, positionally paired with the widgets in order.", + "items": { + "type": "number" + } + } + } + }, + "widget_order": { + "type": "array", + "description": "Ordered list of widget ids controlling the entity-details widget-grid order. Each id is a capability widget's `component` (or `summary` for the synthesized summary card); widgets absent from the list keep their natural order at the end.", + "items": { + "type": "string" + } + }, + "widget_visibility": { + "type": "object", + "description": "Per-widget on/off state for the entity-details widget grid, keyed by widget id (a capability widget's `component`, or `summary` for the synthesized summary card). Holds only the admin's deviations from each widget's declared default: a widget absent from this map falls back to its ui_hook `default_visible`, and then to visible. Do not seed this map from migrations — doing so would freeze today's defaults into the schema.", + "additionalProperties": { + "type": "boolean" + }, + "example": { + "next_best_action": true, + "address_map": false + } + }, + "widget_widths": { + "type": "object", + "description": "Per-widget width in the entity-details widget grid, keyed by widget id (a capability widget's `component`, or `summary` for the synthesized summary card). Spans a 12-column grid: `one_third_width` = 4, `half_width` = 6, `two_third_width` = 8, `full_width` = 12. Holds only the admin's deviations from each widget's declared default: a widget absent from this map falls back to its ui_hook `default_width`, and then to `full_width`. Do not seed this map from migrations — doing so would freeze today's defaults into the schema. Replaces the positional `grid_layout` preset.", + "additionalProperties": { + "type": "string", + "enum": [ + "one_third_width", + "half_width", + "two_third_width", + "full_width" + ] + }, + "example": { + "address_map": "full_width", + "recent_communications": "one_third_width" + } } } }, @@ -5094,6 +5259,14 @@ "items": { "$ref": "#/components/schemas/GroupHeadline" } + }, + "conditions": { + "type": "array", + "nullable": true, + "description": "Dimensions that conditional variants of this entity type are keyed by.\n\nAbsent or `null` means entities of this type are not conditional. Attributes whose\nvalues a variant may override are marked with `overridable_attribute`.\n", + "items": { + "$ref": "#/components/schemas/ConditionDefinition" + } } }, "required": [ @@ -5133,11 +5306,13 @@ } }, "frozen": { - "description": "Indicates this schema is currently frozen. Present when the returned version is the frozen version.", + "deprecated": true, + "description": "No longer returned: schema freezing is retired.\n", "type": "boolean" }, "latest": { - "description": "Indicates this is the latest version of the schema. Both frozen and latest can be true if no changes were made since freezing.", + "deprecated": true, + "description": "No longer returned: every read returns the latest version.\n", "type": "boolean" }, "_summary": { @@ -5523,6 +5698,11 @@ "type": "boolean", "default": false }, + "overridable_attribute": { + "type": "boolean", + "default": false, + "description": "Allow conditional variants of the entity to override this attribute's value.\nOnly meaningful on schemas that declare `conditions`. Unflagged attributes stay\nfixed on the base entity.\n" + }, "default_value": {}, "group": { "description": "Which group the attribute should appear in. Accepts group ID or group name", @@ -5549,6 +5729,14 @@ "type": "string", "description": "Defines the conditional rendering expression for showing this field.\nWhen a valid expression is parsed, their evaluation defines the visibility of this attribute.\nNote: Empty or invalid expression have no effect on the field visibility.\n" }, + "data_classification": { + "type": "string", + "enum": [ + "public", + "pii" + ], + "description": "Data classification of the attribute, used by anonymized responses (`?anonymize=true` or tokens minted with `anonymize: true`).\n\n- `pii`: the attribute value is always anonymized in anonymized responses (use to opt in free-text fields containing personal data)\n- `public`: the attribute value is never anonymized (use to opt out fields matched by built-in defaults, e.g. non-personal identifiers)\n\nWhen unset, built-in defaults apply based on the attribute type (email, phone, address, payment) and a curated list of well-known PII fields.\n" + }, "_purpose": { "type": "array", "items": { @@ -7115,12 +7303,163 @@ } ] }, + "BaseConditionDefinition": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Stable identity of the condition, generated by the client when the condition is\ncreated. It is what makes a rename distinguishable from a remove + add, so it must\nbe round-tripped unchanged for the lifetime of the condition.\n", + "example": "d5839b94-ba20-4225-a78e-76951d352bd6" + }, + "name": { + "type": "string", + "pattern": "^[a-z0-9_]+$", + "description": "The key variant values are stored under. Unique within the schema and immutable\nonce the condition has been saved.\n", + "example": "delivery_area" + }, + "label": { + "type": "string", + "description": "Display label. Always editable.", + "example": "Delivery Area" + } + }, + "required": [ + "id", + "name", + "label" + ] + }, + "ScalarConditionDefinition": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseConditionDefinition" + }, + { + "type": "object", + "description": "A condition whose value is a plain scalar, with no further configuration.", + "properties": { + "type": { + "type": "string", + "description": "Immutable once the condition has been saved.", + "enum": [ + "string", + "number", + "date", + "daterange", + "boolean" + ] + } + }, + "required": [ + "type" + ] + } + ] + }, + "SelectConditionDefinition": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseConditionDefinition" + }, + { + "type": "object", + "description": "A condition whose value is one of a fixed set of options.", + "properties": { + "type": { + "type": "string", + "description": "Immutable once the condition has been saved.", + "enum": [ + "select" + ] + }, + "values": { + "type": "array", + "description": "Selectable options. May be empty while the condition is being configured.", + "items": { + "type": "string" + }, + "example": [ + "12_months", + "24_months" + ] + } + }, + "required": [ + "type" + ] + } + ] + }, + "LocationConditionDefinition": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseConditionDefinition" + }, + { + "type": "object", + "description": "A condition whose value identifies a place.", + "properties": { + "type": { + "type": "string", + "description": "Immutable once the condition has been saved.", + "enum": [ + "location" + ] + }, + "format": { + "type": "string", + "description": "The shape of the location value. Immutable once the condition has been saved.", + "enum": [ + "zipcode", + "zipcode_town" + ] + } + }, + "required": [ + "type" + ] + } + ] + }, + "ConditionDefinition": { + "description": "One dimension that conditional variants of an entity type are keyed by. `type`\ndiscriminates the variants: only a `select` condition carries `values`, and only a\n`location` condition carries `format`.\n", + "oneOf": [ + { + "$ref": "#/components/schemas/ScalarConditionDefinition" + }, + { + "$ref": "#/components/schemas/SelectConditionDefinition" + }, + { + "$ref": "#/components/schemas/LocationConditionDefinition" + } + ] + }, "EntitySlug": { "description": "URL-friendly identifier for the entity schema", "type": "string", "pattern": "^[a-zA-Z0-9_-]+$", "example": "contact" }, + "AdjustInstallmentCapabilityConfig": { + "description": "Configuration for adjusting a contract installment through a Journey.", + "type": "object", + "additionalProperties": false, + "example": { + "journey_id": "adjust-installment-flow" + }, + "required": [ + "journey_id" + ], + "properties": { + "journey_id": { + "type": "string", + "minLength": 1, + "description": "ID of the Flow Journey to open when adjusting the installment." + } + } + }, "EntityCapability": { "description": "Capabilities the Entity has. Turn features on/off for entities.", "type": "object", @@ -7146,6 +7485,11 @@ "$ref": "#/components/schemas/Attribute" } }, + "overridable_attribute": { + "type": "boolean", + "default": false, + "description": "Allow conditional variants of the entity to override the values managed by this\ncapability. Only meaningful on schemas that declare `conditions` and on capabilities\nthat render an attribute group (`EntityAttributes:Group` ui hook), e.g. pricing tiers.\nAttributes carried by the capability opt in individually via their own\n`overridable_attribute` flag.\n" + }, "_purpose": { "type": "array", "items": { @@ -7167,6 +7511,9 @@ "description": "ID of the app if the capability is provided by an app", "example": "123e4567-e89b-12d3-a456-426614174000" }, + "adjust_installment": { + "$ref": "#/components/schemas/AdjustInstallmentCapabilityConfig" + }, "ui_config": { "type": "object", "properties": { @@ -7250,6 +7597,27 @@ "required": [ "action" ] + }, + "default_visible": { + "type": "boolean", + "description": "Whether this widget is switched on by default for entity types that have the capability. Only meaningful on the `EntityDetailsV2:Widget` hook. Defaults to true when omitted. An admin's explicit choice in the entity builder is stored in the schema's `ui_config.widget_visibility` and wins over this.", + "example": false + }, + "default_width": { + "type": "string", + "enum": [ + "one_third_width", + "half_width", + "two_third_width", + "full_width" + ], + "description": "How wide this widget is by default in the entity-details widget grid. Only meaningful on the `EntityDetailsV2:Widget` and `EntityDetailsV2:Header` hooks. Defaults to `full_width` when omitted — deliberately unlike the attribute layout default of `half_width`, because a full-width widget can never leave a hole in a row. An admin's explicit choice in the entity builder is stored in the schema's `ui_config.widget_widths` and wins over this.", + "example": "half_width" + }, + "pricing_tier_setting": { + "type": "string", + "description": "The pricing-tier settings key an organisation must have enabled to use this widget. Omit for widgets available to everyone. Resolved against `GET /v2/pricing-tiers/me` as `override_settings[key].enabled ?? settings[key].enabled`. Note this is commercial packaging enforced in the UI, not a security control — the key must also be added to the pricing tier settings list in epilot-admin-portal, or it can never be switched on for a tier.", + "example": "entity_address_maps" } }, "required": [ @@ -8090,6 +8458,7 @@ "from": {}, "size": {}, "hydrate": {}, + "anonymize": {}, "fields": {}, "aggs": {}, "include_deleted": {}, @@ -8174,6 +8543,7 @@ "from": {}, "size": {}, "hydrate": {}, + "anonymize": {}, "fields": {}, "aggs": {}, "include_deleted": {}, @@ -8398,6 +8768,11 @@ "description": "When true, enables entity hydration to resolve nested $relation & $relation_ref references in-place.", "default": false }, + "anonymize": { + "type": "boolean", + "description": "When true, anonymizes PII in the response: identifiers are replaced with deterministic pseudonyms,\naddresses are generalized and well-known free-text fields are redacted.\nAnonymization is forced (regardless of this parameter) when the access token was created with `anonymize: true`.\n", + "default": false + }, "fields": { "$ref": "#/components/schemas/FieldsParam" }, @@ -8626,6 +9001,39 @@ "description": "Optional array of field names to include in the hydrated entity response for this node.\nWhen specified, only the requested fields plus required internal fields (_id, _schema, _org) will be returned.\nOnly applies when hydrate=true.\n" } ] + }, + "filter": { + "type": "array", + "description": "Narrows this node's traversal results to entities matching every filter (AND semantics). Useful for\ndisambiguating among multiple entities reachable via the same graph edge.\n", + "items": { + "$ref": "#/components/schemas/GraphNodeFilter" + } + } + } + }, + "GraphNodeFilter": { + "type": "object", + "description": "Entities are included in this node's result only if `attribute` exactly equals the literal `value`.", + "required": [ + "attribute", + "value" + ], + "properties": { + "attribute": { + "type": "string", + "description": "Entity attribute name to match against. Must be a plain attribute name, not an Elasticsearch field path.", + "example": "order_number" + }, + "value": { + "allOf": [ + { + "$ref": "#/components/schemas/SearchFilterValue" + }, + { + "description": "Literal value the attribute must exactly equal for the entity to be included in this node's result." + } + ], + "example": "OR-113" } } }, @@ -9030,6 +9438,9 @@ } } }, + "source_context": { + "$ref": "#/components/schemas/EntityOperationSourceContext" + }, "_workflow_origin": { "type": "object", "description": "Internal property for workflow origin tracking and infinite loop prevention.\nPopulated when an entity update originates from a workflow execution.\nThis allows downstream automation services to prevent circular triggering.\n", @@ -9043,6 +9454,13 @@ "description": "The flow template ID - used to detect and prevent circular triggering" } } + }, + "_automation_chain": { + "type": "array", + "description": "Ordered automation flow ids that caused this change; used to break multi-automation trigger loops.", + "items": { + "type": "string" + } } }, "required": [ @@ -9051,6 +9469,59 @@ "operation" ] }, + "EntityOperationSourceContext": { + "type": "object", + "description": "Billing and audit source context for the operation event.", + "properties": { + "source": { + "type": "string", + "description": "Normalized source bucket for the operation.", + "enum": [ + "portal", + "epilot", + "erp", + "system", + "api", + "external", + "journey", + "automation", + "unknown" + ] + }, + "source_label": { + "type": "string", + "description": "Original free-form source label from the caller or changeset, when available." + }, + "source_system": { + "type": "string", + "description": "Optional upstream system label, for example an ERP system name." + }, + "source_reference": { + "type": "string", + "description": "Optional upstream change reference." + }, + "actor_type": { + "type": "string", + "enum": [ + "user", + "portal_user", + "api_client", + "automation", + "system" + ] + }, + "actor_id": { + "type": "string" + }, + "effective_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "source" + ] + }, "BaseActivityItem": { "allOf": [ { @@ -9192,6 +9663,11 @@ "user_id": { "type": "string", "example": "10598" + }, + "org_id": { + "type": "string", + "description": "The organization ID of the user who created the view. Used for partner access control.", + "example": "739224" } } }, @@ -9372,7 +9848,8 @@ "message", "portal_user", "request", - "comment" + "comment", + "user" ] }, "TaxonomySearchIncludeArchivedParam": { @@ -10052,6 +10529,16 @@ } }, "parameters": { + "AnonymizeQueryParam": { + "name": "anonymize", + "description": "When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced\nwith deterministic pseudonyms (stable within an org), addresses are generalized to postal code / city / country,\nand well-known free-text fields (e.g. note content) are redacted.\n\nUseful for AI agents and data analysis use cases that must not access personal data.\n\nAnonymization is forced (regardless of this parameter) when the access token was created with `anonymize: true`.\n", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + }, "EntityIdPathParam": { "name": "id", "description": "Entity id", diff --git a/packages/cli/definitions/environments.json b/packages/cli/definitions/environments.json index 85120c98e..6b9ef4d45 100644 --- a/packages/cli/definitions/environments.json +++ b/packages/cli/definitions/environments.json @@ -20,7 +20,7 @@ "/v1/environments": { "get": { "operationId": "listEnvironmentVariables", - "summary": "List environment variables", + "summary": "listEnvironmentVariables", "description": "List all environment variables for the organization. Returns metadata only, no secret values.", "tags": [ "environments" @@ -49,8 +49,8 @@ }, "post": { "operationId": "createEnvironmentVariable", - "summary": "Create environment variable", - "description": "Create a new environment variable or secret for the organization.", + "summary": "createEnvironmentVariable", + "description": "Create a new environment variable or secret for the organization. If `group` is provided and the group does not yet exist, it is created automatically.", "tags": [ "environments" ], @@ -96,7 +96,7 @@ "/v1/environments/groups": { "get": { "operationId": "listEnvironmentGroups", - "summary": "List environment groups", + "summary": "listEnvironmentGroups", "description": "List all environment groups for the organization.", "tags": [ "environments" @@ -138,6 +138,7 @@ "put": { "operationId": "putEnvironmentGroup", "summary": "putEnvironmentGroup", + "description": "Create or update an environment group by name. Acts as an upsert — creates the group if it does not exist.", "tags": [ "environments" ], @@ -185,7 +186,7 @@ }, "delete": { "operationId": "deleteEnvironmentGroup", - "summary": "Delete an environment group", + "summary": "deleteEnvironmentGroup", "description": "Deletes a group. Variables assigned to this group become ungrouped.", "tags": [ "environments" @@ -224,8 +225,8 @@ ], "get": { "operationId": "getEnvironmentVariable", - "summary": "Get environment variable", - "description": "Get an environment variable by key. Returns value only for String type, omitted for SecretString.", + "summary": "getEnvironmentVariable", + "description": "Get an environment variable by key. Returns value for non-secret types, omitted for SecretString.", "tags": [ "environments" ], @@ -256,8 +257,8 @@ }, "put": { "operationId": "updateEnvironmentVariable", - "summary": "Update environment variable", - "description": "Create or update an environment variable. Acts as an upsert — creates the variable if it does not exist.", + "summary": "updateEnvironmentVariable", + "description": "Create or update an environment variable. Acts as an upsert — creates the variable if it does not exist. If `group` is provided and the group does not yet exist, it is created automatically.", "tags": [ "environments" ], @@ -301,6 +302,9 @@ "403": { "description": "Forbidden" }, + "409": { + "description": "Cannot change the type of a variable that currently holds a value" + }, "500": { "description": "Internal server error" } @@ -308,7 +312,7 @@ }, "delete": { "operationId": "deleteEnvironmentVariable", - "summary": "Delete environment variable", + "summary": "deleteEnvironmentVariable", "description": "Delete an environment variable by key.", "tags": [ "environments" @@ -344,9 +348,90 @@ "schemas": { "EnvironmentValueType": { "type": "string", + "description": "The structure a variable's value holds. `SecretString` is encrypted at rest and\nits value is never returned. `Text`, `Number`, `Boolean` and `Map` may be\nserved to browser-facing consumers; `String` and `SecretString` may not.\n", "enum": [ "String", - "SecretString" + "SecretString", + "Text", + "Number", + "Boolean", + "Map" + ] + }, + "StringTranslations": { + "description": "A string translated per language. Keys are language codes (e.g. `de`,\n`en-US`), matching the hyphen-only BCP-47 form epilot's i18n stack uses\neverywhere else. Must match LANGUAGE_KEY_PATTERN in\nsrc/core/value-types.ts — the two are not otherwise linked.\n", + "type": "object", + "minProperties": 1, + "propertyNames": { + "pattern": "^[a-z]{2,3}(-[A-Za-z0-9]+)*$" + }, + "additionalProperties": { + "type": "string", + "minLength": 1 + } + }, + "MapEntry": { + "description": "One entry of a Map. `key` is the token a journey submits; `value` is\nwhat the customer reads — either one string, or one string per\nlanguage. Every entry of a Map must agree on which of the two it uses.\n", + "type": "object", + "required": [ + "key", + "value" + ], + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "minLength": 1 + }, + "value": { + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "$ref": "#/components/schemas/StringTranslations" + } + ] + } + } + }, + "MapValue": { + "type": "object", + "required": [ + "options" + ], + "additionalProperties": false, + "properties": { + "fallbackLanguage": { + "type": "string", + "minLength": 2, + "default": "de" + }, + "options": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/MapEntry" + } + } + } + }, + "EnvironmentValue": { + "description": "A variable's value. The JSON type corresponds to the variable's `type`:\n`String`, `SecretString` and `Text` are strings, `Number` is a number,\n`Boolean` is a boolean, and `Map` is an object. Numbers are IEEE 754\ndoubles; integers above 2^53 may lose precision on round-trip.\n", + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "$ref": "#/components/schemas/MapValue" + } ] }, "EnvironmentVariable": { @@ -373,8 +458,16 @@ "description": "Optional group name for organising variables in the UI" }, "value": { - "type": "string", - "description": "Value is returned for String type, omitted for SecretString" + "allOf": [ + { + "$ref": "#/components/schemas/EnvironmentValue" + } + ], + "description": "Returned for non-secret types, omitted for SecretString. Also omitted when\nthe variable has been created without a value — for example by a blueprint\ninstall, which syncs a variable's key and type but never its value.\n" + }, + "protected": { + "type": "boolean", + "description": "Whether the variable is protected from editing" }, "created_at": { "type": "string", @@ -409,8 +502,16 @@ "description": "Optional group name for organising variables in the UI" }, "value": { - "type": "string", - "description": "Value is returned for String type, omitted for SecretString" + "allOf": [ + { + "$ref": "#/components/schemas/EnvironmentValue" + } + ], + "description": "Returned for non-secret types, omitted for SecretString. Also omitted when\nthe variable has been created without a value — for example by a blueprint\ninstall, which syncs a variable's key and type but never its value.\n" + }, + "protected": { + "type": "boolean", + "description": "Whether the variable is protected from editing" }, "created_at": { "type": "string", @@ -457,7 +558,11 @@ "type": "string" }, "value": { - "type": "string" + "$ref": "#/components/schemas/EnvironmentValue" + }, + "protected": { + "type": "boolean", + "description": "Whether the variable is protected from editing" } } }, @@ -473,13 +578,17 @@ ] }, "value": { - "type": "string" + "$ref": "#/components/schemas/EnvironmentValue" }, "description": { "type": "string" }, "group": { "type": "string" + }, + "protected": { + "type": "boolean", + "description": "Whether the variable is protected from editing" } } }, diff --git a/packages/cli/definitions/event-catalog.json b/packages/cli/definitions/event-catalog.json index 7f05180fe..2a16f5648 100644 --- a/packages/cli/definitions/event-catalog.json +++ b/packages/cli/definitions/event-catalog.json @@ -48,6 +48,39 @@ } } } + }, + "post": { + "operationId": "createCustomEvent", + "summary": "createCustomEvent", + "description": "Reserve an org-scoped custom event name and persist its immutable v1.0 draft definition.", + "tags": [ + "Event Catalog" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCustomEventPayload" + } + } + } + }, + "responses": { + "201": { + "description": "Custom event draft created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventConfig" + } + } + } + }, + "409": { + "description": "Name is reserved by a built-in or existing org event" + } + } } }, "/v1/events/{event_name}": { @@ -125,6 +158,127 @@ "description": "Event not found" } } + }, + "delete": { + "operationId": "deprecateCustomEvent", + "summary": "deprecateCustomEvent", + "description": "Soft-deprecate an org-scoped custom event. Definitions and v1.0 history remain readable.", + "tags": [ + "Event Catalog" + ], + "parameters": [ + { + "name": "event_name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Custom event deprecated" + }, + "404": { + "description": "Event not found" + }, + "409": { + "description": "Built-ins cannot be deprecated or lifecycle cannot transition" + } + } + } + }, + "/v1/events/{event_name}:preview": { + "post": { + "operationId": "previewCustomEvent", + "summary": "previewCustomEvent", + "description": "Assemble and fully validate a persisted custom-event draft without publishing it.", + "tags": [ + "Event Catalog" + ], + "parameters": [ + { + "name": "event_name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TriggerEventPayload" + } + } + } + }, + "responses": { + "200": { + "description": "Deterministic preview result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewEventResponse" + } + } + } + }, + "400": { + "description": "Projection or schema validation failed" + }, + "404": { + "description": "Custom event not found" + } + } + } + }, + "/v1/events/{event_name}:publish": { + "post": { + "operationId": "publishCustomEventDefinition", + "summary": "publishCustomEventDefinition", + "description": "Conditionally activate an immutable custom-event v1.0 definition.", + "tags": [ + "Event Catalog" + ], + "parameters": [ + { + "name": "event_name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublishCustomEventPayload" + } + } + } + }, + "responses": { + "200": { + "description": "Published event configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventConfig" + } + } + } + }, + "409": { + "description": "Definition is not a draft or base publication choice is invalid" + } + } } }, "/v1/events/{event_name}/json_schema": { @@ -437,7 +591,7 @@ "post": { "operationId": "triggerEvent", "summary": "triggerEvent", - "description": "Explicitly trigger an event by providing input field values and an optional entity seed\nfor graph hydration. The event must be enabled for the organization.\n\n- For events with an entity_graph, a seed (entity_id + node_id) is required\n- For events without an entity_graph, only fields are needed\n- Entity operation context fields (operation, trigger_entity, activity_id, activity_type)\n are not included when triggering via API\n", + "description": "Explicitly trigger an event by providing input field values and an optional entity seed\nfor graph hydration. The event must be enabled for the organization.\n\n- For events with an entity_graph, a seed (entity_id + node_id) is required\n- For events without an entity_graph, only fields are needed\n- Entity operation context fields (operation, trigger_entity, activity_id, activity_type)\n are not included when triggering via API\n- Events marked `automation_trigger_only` require `_trigger_source_type: automation` and a\n stable execution/action ID in `_trigger_source`; retries with that ID return the same\n logical event, including while durable delivery is pending\n", "tags": [ "Event Catalog" ], @@ -464,7 +618,7 @@ }, "responses": { "200": { - "description": "Event triggered successfully", + "description": "Event published successfully", "content": { "application/json": { "schema": { @@ -481,6 +635,33 @@ }, "404": { "description": "Event not found" + }, + "409": { + "description": "The Automation trigger source was already used with different trigger input, including a different entity seed" + }, + "425": { + "description": "The hydrated entity graph is not ready yet, another request currently owns the\nidempotency lease, or a durably queued event (including an already-requested redrive)\nis awaiting publication. Automation must retry with the same trigger source and may\nadvance only after a 200 response.\n", + "headers": { + "Retry-After": { + "description": "Seconds before the Automation action should retry", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "503": { + "description": "Durable delivery exhausted its bounded automatic redrives. The same logical event is\nretained and no new event is created. An API retry with the same trigger source\nrequests a redrive of the existing Standard workflow from its failed task checkpoint\nand receives this retryable response. Subsequent retries receive 425 while that\nredrive is pending or running.\n", + "headers": { + "Retry-After": { + "description": "Seconds before the Automation action should retry", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } } } } @@ -593,6 +774,51 @@ "description": "Whether this event can be explicitly triggered by automations.\nWhen true, the event will appear in the automation builder as a\n\"Trigger Event\" action option.\nDefaults to false if not specified.\n", "default": false, "example": true + }, + "automation_trigger_only": { + "type": "boolean", + "readOnly": true, + "description": "Whether explicit triggering is restricted to Automation. When true, callers must pass\n`_trigger_source_type: automation` and a stable `_trigger_source`. The trigger uses\nstrict entity readiness validation and the durable Automation outbox.\nRequires `automation_trigger: true` and cannot be combined with `entity_operation`.\n", + "default": false, + "example": true + }, + "automation_trigger_seed_node": { + "type": "string", + "readOnly": true, + "description": "Required entity-graph seed node for an Automation-only trigger. When configured, an\nexplicit trigger using any other node is rejected before hydration.\n", + "example": "ticket" + }, + "event_origin": { + "type": "string", + "readOnly": true, + "enum": [ + "builtin", + "custom" + ], + "description": "Definition ownership. Built-ins always win a name collision." + }, + "mapping": { + "$ref": "#/components/schemas/EventMapping" + }, + "lineage": { + "$ref": "#/components/schemas/CustomEventLineage" + }, + "success_criteria": { + "type": "array", + "description": "Org-defined success criteria for this event: the entity attributes that an\norganization considers must be captured for an event change request to be\ntreated as complete (e.g. for telephony / self-service flows).\n\nAdvisory metadata — event-catalog does NOT require an org to define any and\ndoes NOT enforce them when an event is triggered or published. The org may\ndefine none (empty array or omitted). When provided, each entry is validated\nfor well-formedness on write (see SuccessCriterion).\n", + "items": { + "$ref": "#/components/schemas/SuccessCriterion" + }, + "example": [ + { + "entity_schema": "contract", + "attribute": "installment_amount" + }, + { + "entity_schema": "billing_account", + "attribute": "due_date" + } + ] } } }, @@ -612,17 +838,187 @@ } ] }, - "UpdateEventPayload": { - "description": "Payload for updating an event configuration.\nAccepts the same fields as EventConfig (all optional for PATCH).\nCurrently `enabled` and `auto_trigger` fields are processed.\n", - "allOf": [ - { - "$ref": "#/components/schemas/EventConfigBase" + "CreateCustomEventPayload": { + "type": "object", + "description": "Complete immutable custom-event v1.0 definition. Publication is a separate conditional action.", + "properties": { + "event_name": { + "type": "string", + "pattern": "^[A-Z][A-Za-z0-9]{2,79}$" }, - { - "type": "object" + "event_title": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "event_description": { + "type": "string", + "maxLength": 2000 + }, + "event_tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "schema_fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/CustomSchemaField" + } + }, + "entity_graph": { + "$ref": "#/components/schemas/GraphDefinition" + }, + "entity_operation": { + "$ref": "#/components/schemas/EntityOperationTrigger" + }, + "automation_trigger": { + "type": "boolean", + "default": true + }, + "mapping": { + "$ref": "#/components/schemas/EventMapping" + }, + "lineage": { + "$ref": "#/components/schemas/CustomEventLineage" + }, + "example": { + "type": "object", + "additionalProperties": true + } + }, + "required": [ + "event_name", + "event_title", + "schema_fields" + ] + }, + "EventMapping": { + "type": "object", + "description": "Guided mappings use schema_fields graph_source expressions; raw mode evaluates one JSONata object transform.", + "properties": { + "mode": { + "type": "string", + "enum": [ + "guided", + "jsonata" + ], + "default": "guided" + }, + "jsonata": { + "type": "string", + "minLength": 1, + "maxLength": 20000 + } + }, + "required": [ + "mode" + ] + }, + "CustomEventLineage": { + "type": "object", + "description": "Optional catalog lineage to a separately named base event. It does not replace the base event.", + "properties": { + "base_event_name": { + "type": "string" + }, + "base_event_version": { + "type": "string" + } + }, + "required": [ + "base_event_name", + "base_event_version" + ] + }, + "PurposeFilterSnapshot": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "display_name": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "display_name" + ] + }, + "PublishCustomEventPayload": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "auto_trigger": { + "type": "boolean", + "default": true + }, + "base_auto_trigger_enabled": { + "type": "boolean", + "description": "Optional publication choice. False disables only the named lineage base event's org auto-trigger overlay." + } + } + }, + "ValidationIssue": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "message": { + "type": "string" } + }, + "required": [ + "path", + "message" ] }, + "PreviewEventResponse": { + "type": "object", + "properties": { + "payload": { + "type": "object", + "additionalProperties": true + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationIssue" + } + } + }, + "required": [ + "payload", + "errors" + ] + }, + "UpdateEventPayload": { + "type": "object", + "description": "Mutable org activation overlay. Immutable event definition fields are not accepted.", + "properties": { + "enabled": { + "type": "boolean" + }, + "auto_trigger": { + "type": "boolean" + }, + "success_criteria": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SuccessCriterion" + } + } + }, + "additionalProperties": false + }, "PrimitiveField": { "type": "object", "description": "A primitive JSON Schema field definition", @@ -683,6 +1079,17 @@ "items" ] }, + "CustomSchemaField": { + "description": "Custom v1 fields support JSON Schema values and context entities; attachment semantics are built-in-only.", + "oneOf": [ + { + "$ref": "#/components/schemas/PrimitiveField" + }, + { + "$ref": "#/components/schemas/ContextEntity" + } + ] + }, "SchemaField": { "oneOf": [ { @@ -696,6 +1103,26 @@ } ] }, + "SuccessCriterion": { + "type": "object", + "description": "A single org-defined success criterion: an entity attribute that must be captured\nfor this event's change request to be considered complete.\n\nIdentity is the entity schema plus the attribute name — mirroring the\nEntityOperationTrigger `schema`/`attribute` vocabulary. On write (PATCH), both\n`attribute` and `entity_schema` are required and `entity_schema` must match the\n`schema` of a node in the event's `entity_graph` (else the request is rejected) —\nthis prevents unsatisfiable criteria. The criteria themselves are advisory and are\nnever enforced when an event is triggered or published.\n", + "properties": { + "entity_schema": { + "type": "string", + "description": "Entity schema slug the attribute belongs to (matches a node schema in the event's entity_graph).", + "example": "contract" + }, + "attribute": { + "type": "string", + "description": "Attribute name on the entity schema.", + "example": "installment_amount" + } + }, + "required": [ + "entity_schema", + "attribute" + ] + }, "CommonEventMetadata": { "type": "object", "description": "Common metadata fields present in all event payloads", @@ -878,6 +1305,24 @@ ] } }, + "InlineDowngradeStep": { + "type": "object", + "description": "One step of an event's inline `_downgrades` chain. Maps the current-version payload to the previous version via a JSONata expression. Stamped by Event Catalog at publish time; executed by consumers during walk-back, never by EC itself.", + "properties": { + "to": { + "type": "string", + "description": "Version label this step downgrades to (the previous version)." + }, + "jsonata": { + "type": "string", + "description": "JSONata expression mapping the current-shape payload to the previous-shape payload." + } + }, + "required": [ + "to", + "jsonata" + ] + }, "Event": { "type": "object", "description": "An event instance in the event history", @@ -920,6 +1365,20 @@ "_ack_id": { "type": "string", "description": "Unique acknowledgment tracking ID for the event.\nUsed to track event delivery and processing status.\n" + }, + "_downgrades": { + "type": "array", + "description": "Inline downgrade chain stamped by Event Catalog at publish time, ordered newest-to-oldest. Present ONLY on multi-version events. Internal versioning transport: consumers (e.g. svc-webhooks) walk the payload back to a pinned version using these JSONata steps, then strip the field before delivery -- end customers never receive it.", + "items": { + "$ref": "#/components/schemas/InlineDowngradeStep" + } + }, + "_automation_chain": { + "type": "array", + "description": "Ordered automation flow ids that caused this event (at most 100), propagated verbatim from the trigger input or the originating entity operation. Internal loop-prevention transport for automation-api; svc-webhooks strips it before delivery.", + "items": { + "type": "string" + } } }, "required": [ @@ -1146,6 +1605,13 @@ "Kündigung", "Umzug/Auszug" ] + }, + "purpose_filters": { + "type": "array", + "description": "Stable purpose IDs plus immutable display-name snapshots for custom events.", + "items": { + "$ref": "#/components/schemas/PurposeFilterSnapshot" + } } }, "required": [ @@ -1238,6 +1704,14 @@ "TriggerEventPayload": { "type": "object", "description": "Payload for explicitly triggering an event via API", + "example": { + "seed": { + "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "ticket" + }, + "_trigger_source_type": "automation", + "_trigger_source": "execution-id/action-id" + }, "properties": { "seed": { "type": "object", @@ -1276,7 +1750,14 @@ }, "_trigger_source": { "type": "string", - "description": "Identifier of the specific trigger source.\nExamples: user ID, automation execution ID, activity ID, portal user email\nDefaults to the calling user ID if not specified.\n" + "description": "Identifier of the specific trigger source.\nExamples: user ID, automation execution ID, activity ID, portal user email\nDefaults to the calling user ID if not specified.\nRequired for events marked `automation_trigger_only` and must remain stable across\naction retries.\n" + }, + "_automation_chain": { + "type": "array", + "description": "Ordered automation flow ids that caused this trigger (at most 100); propagated verbatim onto the published event for automation loop prevention.", + "items": { + "type": "string" + } } } }, @@ -1286,7 +1767,7 @@ "properties": { "success": { "type": "boolean", - "description": "Whether the event was triggered successfully" + "description": "Whether the event was published successfully" }, "event_id": { "type": "string", @@ -1294,7 +1775,7 @@ }, "event_bridge_event_id": { "type": "string", - "description": "EventBridge event ID from publishing" + "description": "EventBridge event ID when synchronous delivery or a completed retry provides it" } }, "required": [ diff --git a/packages/cli/definitions/file.json b/packages/cli/definitions/file.json index 34d654638..50c262cb1 100644 --- a/packages/cli/definitions/file.json +++ b/packages/cli/definitions/file.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "File API", - "version": "1.10.0", + "version": "1.13.0", "description": "The File API enables you to upload, store, manage, and share files within the epilot platform.\n\n## Key Features\n- **Upload files** to temporary storage and save them permanently as File entities\n- **Generate previews** (thumbnails) for images and documents\n- **Create public links** to share private files externally\n- **Organize files** into collections for better management\n- **Version control** with automatic file versioning on updates\n\n## File Upload Workflow\n1. Call `uploadFileV2` to get a pre-signed S3 URL\n2. Upload your file directly to S3 using the pre-signed URL (PUT request)\n3. Call `saveFileV2` with the S3 reference to create a permanent File entity\n\n## Changelog\nView API Changelog\n" }, "tags": [ @@ -557,7 +557,7 @@ "get": { "operationId": "downloadFile", "summary": "downloadFile", - "description": "Generate a pre-signed download URL for a file.\n\nThe returned URL is valid for a limited time (typically 15 minutes) and can be used to download the file directly.\n", + "description": "Generate a pre-signed download URL for a file.\n\nThe returned URL is valid for a limited time (typically 15 minutes) and can be used to download the file directly.\n\n**Download attribution (`x-track-*`):** any query parameter whose name starts with\n`x-track-` is treated as opaque tracking metadata and signed into the returned URL, so it\nshows up in the S3 access logs / CloudTrail data events when the link is actually opened.\nThis is a generic, service-agnostic channel — e.g. the customer portal sends\n`x-track-portal-id` and `x-track-contact-id`. Only the `x-track-` namespace is forwarded;\nthe values cannot affect how S3 resolves or serves the object. Parameter names must match\n`^x-track-[a-z0-9](?:[a-z0-9_-]{0,38}[a-z0-9])?$`, values are capped at 256 characters, and\nat most 10 such parameters are honoured (see the `x-track` parameter below).\n", "tags": [ "File" ], @@ -588,6 +588,20 @@ "type": "boolean", "default": true } + }, + { + "name": "x-track", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "description": "Download-attribution channel. Provide any number of query parameters whose names\nstart with `x-track-` (e.g. `x-track-portal-id`, `x-track-contact-id`); each is signed\ninto the pre-signed URL verbatim so the download is recorded in the S3 access logs /\nCloudTrail data events. Only the `x-track-` namespace is forwarded — values cannot\naffect how S3 resolves or serves the object. Parameter names must match\n`^x-track-[a-z0-9](?:[a-z0-9_-]{0,38}[a-z0-9])?$`, values are capped at 256 characters,\nand at most 10 such parameters are honoured.\n", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } } ], "responses": { @@ -640,11 +654,11 @@ "name": "s3_key", "in": "query", "required": true, - "description": "The S3 object key", + "description": "The S3 object key, as returned in `s3ref.key` of the file entity.\n\nNote: object keys store the filename segment percent-encoded. For example, a file named\n`Straße 1.pdf` is stored under the key `.../Stra%C3%9Fe%201.pdf`, and that is the value\n`s3ref.key` contains. Pass it exactly as returned by the API and make sure your HTTP client\nURL-encodes the query parameter value, so a literal `%` arrives encoded as `%25`.\n", "schema": { "type": "string" }, - "example": "123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf" + "example": "123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/Stra%C3%9Fe%201.pdf" }, { "name": "s3_bucket", @@ -691,6 +705,9 @@ "401": { "$ref": "#/components/responses/UnauthorizedError" }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, "404": { "$ref": "#/components/responses/NotFoundError" }, @@ -875,11 +892,320 @@ } } }, + "/v1/files/{id}/summary": { + "get": { + "operationId": "getFileSummary", + "summary": "getFileSummary", + "description": "Get summary text for a file entity together with the current summary job status when available.", + "tags": [ + "File" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The UUID of the file entity", + "schema": { + "$ref": "#/components/schemas/FileEntityId" + } + } + ], + "responses": { + "200": { + "description": "Current file summary state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileSummary" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequestError" + }, + "401": { + "$ref": "#/components/responses/UnauthorizedError" + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/files/{id}/summary/feedback": { + "get": { + "operationId": "getFileSummaryFeedback", + "summary": "Get file summary feedback", + "description": "Get the authenticated user's feedback for the current generated file summary.", + "tags": [ + "File" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The UUID of the file entity", + "schema": { + "$ref": "#/components/schemas/FileEntityId" + } + } + ], + "responses": { + "200": { + "description": "Current feedback state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileSummaryFeedbackResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequestError" + }, + "401": { + "$ref": "#/components/responses/UnauthorizedError" + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "put": { + "operationId": "putFileSummaryFeedback", + "summary": "Submit file summary feedback", + "description": "Upsert thumbs up/down feedback for the current generated file summary.", + "tags": [ + "File" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The UUID of the file entity", + "schema": { + "$ref": "#/components/schemas/FileEntityId" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutFileSummaryFeedbackRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Feedback recorded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileSummaryFeedbackResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequestError" + }, + "401": { + "$ref": "#/components/responses/UnauthorizedError" + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" + }, + "409": { + "$ref": "#/components/responses/ConflictError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/files/{id}/summary-jobs": { + "post": { + "operationId": "createFileSummaryJob", + "summary": "createFileSummaryJob", + "description": "Create or return the current AI summary job for a file entity.", + "tags": [ + "File" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The UUID of the file entity", + "schema": { + "$ref": "#/components/schemas/FileEntityId" + } + } + ], + "responses": { + "202": { + "description": "Summary job accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileSummaryJob" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequestError" + }, + "401": { + "$ref": "#/components/responses/UnauthorizedError" + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/files/{id}/summary-jobs/current": { + "get": { + "operationId": "getCurrentFileSummaryJob", + "summary": "getCurrentFileSummaryJob", + "description": "Get the latest AI summary job for the file entity's current source.", + "tags": [ + "File" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The UUID of the file entity", + "schema": { + "$ref": "#/components/schemas/FileEntityId" + } + } + ], + "responses": { + "200": { + "description": "Current summary job", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileSummaryJob" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequestError" + }, + "401": { + "$ref": "#/components/responses/UnauthorizedError" + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/files/{id}/summary-jobs/{job_id}": { + "get": { + "operationId": "getFileSummaryJob", + "summary": "getFileSummaryJob", + "description": "Get an AI summary job by id.", + "tags": [ + "File" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The UUID of the file entity", + "schema": { + "$ref": "#/components/schemas/FileEntityId" + } + }, + { + "name": "job_id", + "in": "path", + "required": true, + "description": "The UUID of the summary job", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Summary job", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileSummaryJob" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequestError" + }, + "401": { + "$ref": "#/components/responses/UnauthorizedError" + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, "/v1/files/{id}/summary:generate": { "post": { "operationId": "generateFileSummary", "summary": "generateFileSummary", - "description": "Request AI generation of preview and short summaries for a file entity.", + "description": "Compatibility alias for creating or returning the current AI summary job for a file entity.", "tags": [ "File" ], @@ -900,7 +1226,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FileSummaryGenerationStatus" + "$ref": "#/components/schemas/FileSummaryJob" } } } @@ -917,8 +1243,53 @@ "404": { "$ref": "#/components/responses/NotFoundError" }, - "409": { - "$ref": "#/components/responses/ConflictError" + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/files/{id}/text": { + "get": { + "operationId": "getFileText", + "summary": "getFileText", + "description": "Get the plain-text representation of a file entity. Returns status `not_ready` while text is being prepared and `unsupported` when text is unavailable for the file or organization.", + "tags": [ + "File" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The UUID of the file entity", + "schema": { + "$ref": "#/components/schemas/FileEntityId" + } + } + ], + "responses": { + "200": { + "description": "Text state for the file entity", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileText" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequestError" + }, + "401": { + "$ref": "#/components/responses/UnauthorizedError" + }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, + "404": { + "$ref": "#/components/responses/NotFoundError" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -993,6 +1364,9 @@ "401": { "$ref": "#/components/responses/UnauthorizedError" }, + "403": { + "$ref": "#/components/responses/ForbiddenError" + }, "404": { "$ref": "#/components/responses/NotFoundError" }, @@ -1247,6 +1621,27 @@ } } }, + "204": { + "description": "No preview could be generated and no redirect target is available\n(the file has no public URL). No body is returned.\n" + }, + "302": { + "description": "The preview could not be optimized (e.g. image dimensions above the input\npixel limit, an oversized file, or an unsupported/undecodable format).\nSince the file is public, the client is redirected to the original file,\nwhich the browser can render directly.\n", + "headers": { + "Location": { + "description": "URL of the original public file", + "schema": { + "type": "string", + "format": "uri" + } + }, + "Cache-Control": { + "description": "Caching policy for the redirect", + "schema": { + "type": "string" + } + } + } + }, "304": { "description": "The preview is unchanged since the version identified by the `If-None-Match`\nrequest header. No body is returned; the cached copy should be reused.\n", "headers": { @@ -2178,6 +2573,12 @@ "description": "Human readable file size", "readOnly": true }, + "etag": { + "type": "string", + "description": "Content hash (S3 ETag) of the latest file version. For single-part uploads this is the hex MD5 of the content and is stable across orgs, so identical content yields an identical etag. Used to detect real content changes without re-downloading the bytes.", + "example": "9bb58f26192e4ba00f01e2e7b136bbd8", + "readOnly": true + }, "access_control": { "type": "string", "default": "private", @@ -2196,6 +2597,9 @@ "custom_download_url": { "$ref": "#/components/schemas/CustomDownloadUrl" }, + "custom_download_url_auth": { + "$ref": "#/components/schemas/CustomDownloadUrlAuth" + }, "preview_summary_de": { "type": "string", "description": "Compact German summary for hover and list preview surfaces.", @@ -2216,8 +2620,11 @@ "description": "Short English paragraph summary for file preview surfaces.", "readOnly": true }, - "summary_status": { - "$ref": "#/components/schemas/FileSummaryStatus" + "file_summary_execution_id": { + "type": "string", + "format": "uuid", + "description": "AI execution that produced the current summary. Used to correlate user feedback.", + "readOnly": true } } }, @@ -2238,22 +2645,254 @@ "unknown" ] }, - "FileSummaryStatus": { + "FileSummaryJobStatus": { "type": "string", - "description": "Current AI summary generation state for the file.", + "description": "Current state of a file summary job.", "enum": [ + "queued", + "waiting_for_extraction", "processing", "completed", "failed", + "unsupported", + "stale" + ], + "readOnly": true + }, + "FileSummaryJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid", + "description": "File summary job ID." + }, + "file_id": { + "$ref": "#/components/schemas/FileEntityId" + }, + "status": { + "$ref": "#/components/schemas/FileSummaryJobStatus" + }, + "error": { + "type": "string", + "description": "Human-readable failure or unsupported reason when available." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "job_id", + "file_id", + "status", + "created_at", + "updated_at" + ] + }, + "FileSummary": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/FileSummaryJobStatus" + }, + "job_id": { + "type": "string", + "format": "uuid", + "description": "Current summary job ID when available." + }, + "feedback_available": { + "type": "boolean", + "description": "Whether the current summary can receive user feedback." + }, + "preview_summary_de": { + "type": "string", + "nullable": true, + "description": "Compact German summary for hover and list preview surfaces." + }, + "short_summary_de": { + "type": "string", + "nullable": true, + "description": "Short German paragraph summary for file preview surfaces." + }, + "preview_summary_en": { + "type": "string", + "nullable": true, + "description": "Compact English summary for hover and list preview surfaces." + }, + "short_summary_en": { + "type": "string", + "nullable": true, + "description": "Short English paragraph summary for file preview surfaces." + } + } + }, + "FileSummaryFeedback": { + "type": "object", + "required": [ + "rating", + "user_id", + "submitted_at" + ], + "properties": { + "rating": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "comment": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "submitted_at": { + "type": "string", + "format": "date-time" + } + } + }, + "FileSummaryFeedbackResponse": { + "type": "object", + "required": [ + "feedback" + ], + "properties": { + "feedback": { + "allOf": [ + { + "$ref": "#/components/schemas/FileSummaryFeedback" + } + ], + "nullable": true + } + } + }, + "PutFileSummaryFeedbackRequest": { + "type": "object", + "required": [ + "rating" + ], + "properties": { + "rating": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "comment": { + "type": "string" + } + } + }, + "FileTextStatus": { + "type": "string", + "description": "Availability of the plain-text representation for a file entity.", + "enum": [ + "ready", + "not_ready", "unsupported" ], "readOnly": true }, - "FileSummaryGenerationStatus": { + "FileText": { "type": "object", "properties": { "status": { - "$ref": "#/components/schemas/FileSummaryStatus" + "$ref": "#/components/schemas/FileTextStatus" + }, + "text": { + "type": "string" + }, + "truncated": { + "type": "boolean" + }, + "total_chars": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "status" + ], + "oneOf": [ + { + "$ref": "#/components/schemas/FileTextReady" + }, + { + "$ref": "#/components/schemas/FileTextNotReady" + }, + { + "$ref": "#/components/schemas/FileTextUnsupported" + } + ] + }, + "FileTextReady": { + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "enum": [ + "ready" + ] + }, + "text": { + "type": "string", + "description": "Plain-text representation, trimmed and truncated to a server-side limit of 50000 characters." + }, + "truncated": { + "type": "boolean", + "description": "True when the returned text was cut off by the character limit." + }, + "total_chars": { + "type": "integer", + "minimum": 0, + "description": "Length of the full text before truncation." + } + }, + "required": [ + "status", + "text", + "truncated", + "total_chars" + ] + }, + "FileTextNotReady": { + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "enum": [ + "not_ready" + ] + } + }, + "required": [ + "status" + ] + }, + "FileTextUnsupported": { + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "enum": [ + "unsupported" + ] } }, "required": [ @@ -2266,6 +2905,16 @@ "format": "uri", "example": "https://some-api-url.com/download?file_id=123" }, + "CustomDownloadUrlAuth": { + "description": "Authorization mode for the custom_download_url. `presigned` (the default) uses an HMAC-signed URL; `token` authorizes the download via the caller's bearer token, matched against the exact stored custom_download_url. In token mode the File API returns the unsigned custom_download_url (no expires_at/signature query params) so the stored url matches exactly.", + "type": "string", + "enum": [ + "presigned", + "token" + ], + "default": "presigned", + "example": "token" + }, "FileEntity": { "allOf": [ { @@ -2437,6 +3086,9 @@ "properties": { "custom_download_url": { "$ref": "#/components/schemas/CustomDownloadUrl" + }, + "custom_download_url_auth": { + "$ref": "#/components/schemas/CustomDownloadUrlAuth" } } } @@ -2818,6 +3470,11 @@ "mime_type": { "type": "string", "example": "image/jpeg" + }, + "etag": { + "type": "string", + "description": "Content hash (S3 ETag) of this file version", + "example": "9bb58f26192e4ba00f01e2e7b136bbd8" } } }, diff --git a/packages/cli/definitions/integration-toolkit.json b/packages/cli/definitions/integration-toolkit.json index e73f11e39..7f8e603ac 100644 --- a/packages/cli/definitions/integration-toolkit.json +++ b/packages/cli/definitions/integration-toolkit.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "Integration Toolkit API", - "version": "1.2.0", + "version": "1.22.0", "description": "API for integrating with external systems in a standardised way." }, "tags": [ @@ -10,6 +10,10 @@ "name": "erp", "description": "ERP integration endpoints" }, + { + "name": "erp-imports", + "description": "ERP pricing file import endpoints" + }, { "name": "trigger", "description": "Endpoints to trigger ERP related actions" @@ -279,6 +283,9 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "404": { + "$ref": "#/components/responses/NotFound" + }, "500": { "$ref": "#/components/responses/InternalServerError" } @@ -332,6 +339,9 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "404": { + "$ref": "#/components/responses/NotFound" + }, "500": { "$ref": "#/components/responses/InternalServerError" } @@ -609,6 +619,167 @@ } } }, + "/v1/erp/updates/direct_simulation": { + "post": { + "operationId": "simulateDirect", + "summary": "simulateDirect", + "description": "Dry run for direct-mode payloads: validates a `DirectPayload` against a `direct: true`\nuse case configuration and previews the internal entity and meter reading updates the\npipeline would apply — without persisting anything.\n\nUnlike the ingest endpoint, validation does not stop at the first problem: ALL issues\nacross all operations are collected into `errors`, each carrying the index of the\noperation it refers to. When the payload is valid, the response additionally contains\nthe translated update previews plus non-blocking `warnings` (for example unique-id\nfields that are not defined in the target entity schema, or a failed server-side\nunique-id type derivation).\n\nUse this endpoint while designing a direct integration, before sending events to\n`POST /v3/erp/updates/events`. See documentation at /docs/DIRECT_MODE.md.\n", + "tags": [ + "erp" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DirectSimulationRequest" + }, + "examples": { + "entity_upsert": { + "summary": "Entity upsert (open mode)", + "value": { + "event_configuration": { + "direct": true + }, + "payload": { + "version": "1", + "operations": [ + { + "entity_slug": "contact", + "unique_ids": { + "customer_number": "CUST-12345" + }, + "attributes": { + "first_name": "Anna", + "last_name": "Schmidt", + "email": "anna.schmidt@example.com" + } + } + ] + } + } + }, + "allowlist_with_relation": { + "summary": "Allowlisted entities with a relation", + "value": { + "event_configuration": { + "direct": true, + "entities": [ + { + "entity_schema": "contract", + "unique_ids": [ + "contract_number" + ] + }, + { + "entity_schema": "contact", + "unique_ids": [ + "customer_number" + ] + } + ] + }, + "payload": { + "version": "1", + "operations": [ + { + "entity_slug": "contract", + "unique_ids": { + "contract_number": "CTR-001" + }, + "attributes": { + "display_name": "Premium Contract", + "customer": { + "$relation": { + "_set": [ + { + "schema": "contact", + "unique_ids": { + "customer_number": "CUST-12345" + }, + "tags": [ + "primary" + ] + } + ] + } + } + } + } + ] + } + } + }, + "meter_reading": { + "summary": "Meter reading", + "value": { + "event_configuration": { + "direct": true + }, + "payload": { + "version": "1", + "operations": [ + { + "type": "meter_reading", + "meter": { + "unique_ids": { + "meter_number": "M-001" + } + }, + "counter": { + "unique_ids": { + "obis_number": "1-0:1.8.0" + } + }, + "reading_matching": "strict-date", + "attributes": { + "external_id": "R-001", + "timestamp": "2026-08-24T06:00:00Z", + "source": "ERP", + "value": 12345.6 + } + } + ] + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Simulation executed - `valid` reports the verdict, `errors` lists every violation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DirectSimulationResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "422": { + "description": "Unprocessable entity - simulation could not be executed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBase" + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, "/v1/erp/updates/mapping_simulation": { "post": { "operationId": "simulateMapping", @@ -1618,29 +1789,41 @@ } } }, - "/v2/integrations": { + "/v1/integrations/{integrationId}/documentation": { "get": { - "operationId": "listIntegrationsV2", - "summary": "listIntegrationsV2", - "description": "Retrieve all integrations with embedded use cases for the authenticated organization", + "operationId": "listDocumentationPages", + "summary": "listDocumentationPages", + "description": "Retrieve all documentation pages of an integration, without their markdown content.\nAn integration has at most one general page plus at most one page per use case.\nThe page id is 'general' for the integration-wide page, otherwise the use case ID.\n", "tags": [ "integrations" ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], "responses": { "200": { - "description": "Successfully retrieved integrations with use cases", + "description": "Successfully retrieved documentation pages", "content": { "application/json": { "schema": { "type": "object", "required": [ - "integrations" + "pages" ], "properties": { - "integrations": { + "pages": { "type": "array", "items": { - "$ref": "#/components/schemas/IntegrationWithUseCases" + "$ref": "#/components/schemas/DocumentationPageSummary" } } } @@ -1651,44 +1834,8 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - }, - "post": { - "operationId": "createIntegrationV2", - "summary": "createIntegrationV2", - "description": "Create a new integration with embedded use cases.\n", - "tags": [ - "integrations" - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpsertIntegrationWithUseCasesRequest" - } - } - } - }, - "responses": { - "201": { - "description": "Integration created successfully with use cases", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IntegrationWithUseCases" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" + "404": { + "description": "Integration not found" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -1696,11 +1843,11 @@ } } }, - "/v2/integrations/{integrationId}": { + "/v1/integrations/{integrationId}/documentation/{docId}": { "get": { - "operationId": "getIntegrationV2", - "summary": "getIntegrationV2", - "description": "Retrieve a specific integration with all its embedded use cases", + "operationId": "getDocumentationPage", + "summary": "getDocumentationPage", + "description": "Retrieve a single documentation page including its markdown content", "tags": [ "integrations" ], @@ -1714,15 +1861,25 @@ "type": "string", "format": "uuid" } + }, + { + "name": "docId", + "in": "path", + "required": true, + "description": "'general' for the integration-wide page, otherwise a use case ID", + "schema": { + "type": "string", + "pattern": "^(general|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$" + } } ], "responses": { "200": { - "description": "Successfully retrieved integration with use cases", + "description": "Successfully retrieved documentation page", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IntegrationWithUseCases" + "$ref": "#/components/schemas/DocumentationPage" } } } @@ -1731,7 +1888,7 @@ "$ref": "#/components/responses/Unauthorized" }, "404": { - "description": "Integration not found" + "description": "Documentation page not found" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -1739,9 +1896,9 @@ } }, "put": { - "operationId": "updateIntegrationV2", - "summary": "updateIntegrationV2", - "description": "Update an existing integration with embedded use cases.\nThe integration must already exist.\nUse cases are updated declaratively:\n- Use cases in the request with matching IDs are updated\n- Use cases in the request without matching IDs are created\n- Existing use cases not in the request are deleted\n", + "operationId": "upsertDocumentationPage", + "summary": "upsertDocumentationPage", + "description": "Create or update the documentation page identified by docId.\nUpsert semantics enforce the invariant of one general page per\nintegration and one page per use case. For use case pages the\nuse case must exist.\n", "tags": [ "integrations" ], @@ -1750,11 +1907,21 @@ "name": "integrationId", "in": "path", "required": true, - "description": "The integration ID (client-provided)", + "description": "The integration ID", "schema": { "type": "string", "format": "uuid" } + }, + { + "name": "docId", + "in": "path", + "required": true, + "description": "'general' for the integration-wide page, otherwise a use case ID", + "schema": { + "type": "string", + "pattern": "^(general|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$" + } } ], "requestBody": { @@ -1762,18 +1929,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpsertIntegrationWithUseCasesRequest" + "$ref": "#/components/schemas/UpsertDocumentationPageRequest" } } } }, "responses": { "200": { - "description": "Integration updated successfully with use cases", + "description": "Documentation page created or updated successfully", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IntegrationWithUseCases" + "$ref": "#/components/schemas/DocumentationPage" } } } @@ -1784,8 +1951,11 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, "404": { - "description": "Integration not found" + "description": "Integration or use case not found" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -1793,9 +1963,9 @@ } }, "delete": { - "operationId": "deleteIntegrationV2", - "summary": "deleteIntegrationV2", - "description": "Delete an integration and all its use cases", + "operationId": "deleteDocumentationPage", + "summary": "deleteDocumentationPage", + "description": "Delete a documentation page", "tags": [ "integrations" ], @@ -1809,11 +1979,21 @@ "type": "string", "format": "uuid" } + }, + { + "name": "docId", + "in": "path", + "required": true, + "description": "'general' for the integration-wide page, otherwise a use case ID", + "schema": { + "type": "string", + "pattern": "^(general|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$" + } } ], "responses": { "200": { - "description": "Integration deleted successfully", + "description": "Documentation page deleted successfully", "content": { "application/json": { "schema": { @@ -1830,8 +2010,11 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, "404": { - "description": "Integration not found" + "description": "Documentation page not found" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -1839,119 +2022,68 @@ } } }, - "/v2/integrations/{integrationId}/use-cases/{useCaseId}/secure-proxy-whitelist": { + "/v2/integrations": { "get": { - "operationId": "getSecureProxyWhitelist", - "summary": "Get secure_proxy whitelist (admin portal only)", - "description": "Returns the current allowed_domains, allowed_ips, and vpc_mode for a secure_proxy use case.\nStaff-only — gated by internal-auth issuer AND admin-portal Cognito user pool membership.\nRejects Login-As tokens.\n", + "operationId": "listIntegrationsV2", + "summary": "listIntegrationsV2", + "description": "Retrieve all integrations with embedded use cases for the authenticated organization", "tags": [ - "integrations", - "proxy" - ], - "security": [ - { - "EpilotAuth": [] - } - ], - "parameters": [ - { - "name": "integrationId", - "in": "path", - "required": true, - "description": "The integration ID", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "useCaseId", - "in": "path", - "required": true, - "description": "The use case ID", - "schema": { - "type": "string", - "format": "uuid" - } - } + "integrations" ], "responses": { "200": { - "description": "Current whitelist", + "description": "Successfully retrieved integrations with use cases", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SecureProxyWhitelist" + "type": "object", + "required": [ + "integrations" + ], + "properties": { + "integrations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IntegrationWithUseCases" + } + } + } } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "description": "Use case not found or not of type secure_proxy" + "500": { + "$ref": "#/components/responses/InternalServerError" } } }, - "put": { - "operationId": "updateSecureProxyWhitelist", - "summary": "Update secure_proxy whitelist (admin portal only)", - "description": "Replaces allowed_domains and/or allowed_ips on a secure_proxy use case.\nAt least one of the two fields is required. Validation mirrors the CLI's\n`validateDomainPatterns` / `validateCidrs`. Writes a USECASE_HISTORY row\nwith the admin user's email as `changed_by`.\n\nUpdate semantics per field:\n - **omitted** — field is not modified; the stored value is preserved.\n - **non-empty array** — the stored value is replaced with the supplied list.\n - **empty array (`[]`)** — the list is cleared (stored as `[]`). This is\n the canonical way to remove all entries. `null` is not accepted.\n\nStaff-only — same auth gates as GET.\n", + "post": { + "operationId": "createIntegrationV2", + "summary": "createIntegrationV2", + "description": "Create a new integration with embedded use cases.\n", "tags": [ - "integrations", - "proxy" - ], - "security": [ - { - "EpilotAuth": [] - } - ], - "parameters": [ - { - "name": "integrationId", - "in": "path", - "required": true, - "description": "The integration ID", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "useCaseId", - "in": "path", - "required": true, - "description": "The use case ID", - "schema": { - "type": "string", - "format": "uuid" - } - } + "integrations" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SecureProxyWhitelistUpdate" + "$ref": "#/components/schemas/UpsertIntegrationWithUseCasesRequest" } } } }, "responses": { - "200": { - "description": "Updated whitelist (same shape as GET)", + "201": { + "description": "Integration created successfully with use cases", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SecureProxyWhitelist" + "$ref": "#/components/schemas/IntegrationWithUseCases" } } } @@ -1962,28 +2094,19 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "description": "Use case not found or not of type secure_proxy" + "500": { + "$ref": "#/components/responses/InternalServerError" } } } }, - "/v2/integrations/{integrationId}/use-cases/{useCaseId}/secure-proxy-whitelist/history": { + "/v2/integrations/{integrationId}": { "get": { - "operationId": "listSecureProxyWhitelistHistory", - "summary": "List secure_proxy whitelist change history (admin portal only)", - "description": "Returns the most recent USECASE_HISTORY entries for a secure_proxy use case,\nin reverse chronological order (newest first). Each entry includes the\nactor email (`changed_by`), the ISO-8601 timestamp (`history_created_at`),\nthe `change_description` (free-text action), and the full `configuration`\nsnapshot — from which UI-08 computes a before/after diff between consecutive\nentries.\n\nStaff-only — gated by internal-auth issuer AND admin-portal Cognito user pool\nmembership. Rejects Login-As tokens (same auth gate as the GET / PUT\nsecure-proxy-whitelist operations).\n\nThin wrapper over the service-layer `listUseCaseHistory` that powers\n`GET /v1/integrations/{integrationId}/use-cases/{useCaseId}/history` — the\nseparate path exists because the /v1 variant is tenant-gated and admin-portal\ninternal-auth tokens do not carry tenant permissions.\n", + "operationId": "getIntegrationV2", + "summary": "getIntegrationV2", + "description": "Retrieve a specific integration with all its embedded use cases", "tags": [ - "integrations", - "proxy" - ], - "security": [ - { - "EpilotAuth": [] - } + "integrations" ], "parameters": [ { @@ -1995,76 +2118,34 @@ "type": "string", "format": "uuid" } - }, - { - "name": "useCaseId", - "in": "path", - "required": true, - "description": "The use case ID (must be of type secure_proxy)", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "description": "Maximum number of history entries to return. Default 10, max 50.\nCapped at the service-layer page size (20) so `limit > 20` is silently\nclamped to 20. UI-08 requests 5-10 for the panel view.\n", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 50, - "default": 10 - } } ], "responses": { "200": { - "description": "Successfully retrieved secure_proxy history", + "description": "Successfully retrieved integration with use cases", "content": { "application/json": { "schema": { - "type": "object", - "required": [ - "history" - ], - "properties": { - "history": { - "type": "array", - "description": "Entries are `SecureProxyUseCaseHistoryEntry` in reverse\nchronological order (newest first). Entries older than the\nrequested `limit` are not returned.\n", - "items": { - "$ref": "#/components/schemas/SecureProxyUseCaseHistoryEntry" - } - } - } + "$ref": "#/components/schemas/IntegrationWithUseCases" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, "404": { - "description": "Use case not found or not of type secure_proxy" + "description": "Integration not found" }, "500": { "$ref": "#/components/responses/InternalServerError" } } - } - }, - "/v1/integrations/{integrationId}/app-mapping": { + }, "put": { - "operationId": "setIntegrationAppMapping", - "summary": "setIntegrationAppMapping", - "description": "Creates or updates a mapping from an app/component to an integration.\nThis allows ERP updates sent via app_id and component_id to be associated\nwith a specific integration configuration.\n", + "operationId": "updateIntegrationV2", + "summary": "updateIntegrationV2", + "description": "Update an existing integration with embedded use cases.\nThe integration must already exist.\nUse cases are updated declaratively:\n- Use cases in the request with matching IDs are updated\n- Use cases in the request without matching IDs are created\n- Existing use cases not in the request are deleted\n", "tags": [ "integrations" ], @@ -2073,7 +2154,7 @@ "name": "integrationId", "in": "path", "required": true, - "description": "The integration ID to map to", + "description": "The integration ID (client-provided)", "schema": { "type": "string", "format": "uuid" @@ -2085,18 +2166,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SetIntegrationAppMappingRequest" + "$ref": "#/components/schemas/UpsertIntegrationWithUseCasesRequest" } } } }, "responses": { "200": { - "description": "App mapping set successfully", + "description": "Integration updated successfully with use cases", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IntegrationAppMapping" + "$ref": "#/components/schemas/IntegrationWithUseCases" } } } @@ -2110,25 +2191,15 @@ "404": { "description": "Integration not found" }, - "409": { - "description": "Mapping already exists (use overwrite=true to replace)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponseBase" - } - } - } - }, "500": { "$ref": "#/components/responses/InternalServerError" } } }, "delete": { - "operationId": "deleteIntegrationAppMapping", - "summary": "deleteIntegrationAppMapping", - "description": "Removes a mapping from an app/component to an integration.\n", + "operationId": "deleteIntegrationV2", + "summary": "deleteIntegrationV2", + "description": "Delete an integration and all its use cases", "tags": [ "integrations" ], @@ -2137,26 +2208,16 @@ "name": "integrationId", "in": "path", "required": true, - "description": "The integration ID (used for authorization, must match the mapping)", + "description": "The integration ID", "schema": { "type": "string", "format": "uuid" } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeleteIntegrationAppMappingRequest" - } - } - } - }, "responses": { "200": { - "description": "App mapping deleted successfully", + "description": "Integration deleted successfully", "content": { "application/json": { "schema": { @@ -2170,14 +2231,11 @@ } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { - "description": "App mapping not found" + "description": "Integration not found" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -2185,15 +2243,19 @@ } } }, - "/v1/integrations/{integrationId}/monitoring/inbound-events": { - "post": { - "operationId": "queryInboundMonitoringEvents", - "summary": "queryInboundMonitoringEvents", - "description": "Query inbound monitoring events for a specific integration.\nReturns detailed information about inbound sync events from ERP systems,\nincluding success rates, error breakdowns, and processing metrics.\n", + "/v2/integrations/{integrationId}/notifications/history": { + "get": { + "operationId": "listNotificationHistory", + "summary": "listNotificationHistory", + "description": "Returns the cursor-paginated, newest-first notification history for an\nintegration (every real notification decision — both fired and suppressed).\nRequires the `integration:view` permission on the integration's organization.\n", "tags": [ - "monitoring", "integrations" ], + "security": [ + { + "EpilotAuth": [] + } + ], "parameters": [ { "name": "integrationId", @@ -2204,21 +2266,47 @@ "type": "string", "format": "uuid" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/QueryInboundMonitoringEventsRequest" - } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "description": "Opaque base64 pagination cursor returned as `next_cursor` by a prior page.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Requested number of items to return. Values above 100 are accepted and clamped server-side to 100 (the enforcement point is the handler, not this schema), so a large value never 400s at the contract layer.", + "schema": { + "type": "integer", + "minimum": 1, + "default": 50 + } + }, + { + "name": "type", + "in": "query", + "required": false, + "description": "Optional notification type filter (e.g. `critical_error`, `error_threshold`).", + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { - "$ref": "#/components/responses/QueryInboundMonitoringEventsResponse" + "description": "Cursor-paginated notification history (newest first)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationHistoryResponse" + } + } + } }, "400": { "$ref": "#/components/responses/BadRequest" @@ -2226,6 +2314,9 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, "404": { "$ref": "#/components/responses/NotFound" }, @@ -2235,64 +2326,18 @@ } } }, - "/v1/integrations/{integrationId}/monitoring/stats": { + "/v2/integrations/{integrationId}/notifications/test": { "post": { - "operationId": "getMonitoringStats", - "summary": "getMonitoringStats", - "description": "Get aggregated statistics for both inbound and outbound monitoring events for a specific integration.\nReturns summary metrics for inbound (ERP sync) and outbound (webhook delivery) events,\nincluding success/error counts and optional breakdowns.\n", + "operationId": "testSendNotification", + "summary": "testSendNotification", + "description": "Renders and sends ONE representative notification of the requested kind/type to\nthe CALLING USER ONLY (never any other recipient), so an operator can preview how\na notification looks. A test send does NOT write to the notification history.\nRequires the `integration:manage` permission on the integration's organization.\n", "tags": [ - "monitoring", "integrations" ], - "parameters": [ + "security": [ { - "name": "integrationId", - "in": "path", - "required": true, - "description": "The integration ID", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetMonitoringStatsRequest" - } - } - } - }, - "responses": { - "200": { - "$ref": "#/components/responses/GetMonitoringStatsResponse" - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" + "EpilotAuth": [] } - } - } - }, - "/v1/integrations/{integrationId}/monitoring/timeseries": { - "post": { - "operationId": "getMonitoringTimeSeries", - "summary": "getMonitoringTimeSeries", - "description": "Get time-series aggregated event counts for monitoring charts.\nReturns pre-bucketed counts at configurable intervals for both inbound and outbound events.\nMaximum of 200 buckets per request. Returns 400 if the time range and interval would exceed this limit.\n", - "tags": [ - "monitoring", - "integrations" ], "parameters": [ { @@ -2311,14 +2356,21 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetMonitoringTimeSeriesRequest" + "$ref": "#/components/schemas/TestNotificationRequest" } } } }, "responses": { - "200": { - "$ref": "#/components/responses/GetMonitoringTimeSeriesResponse" + "202": { + "description": "Test notification accepted for delivery to the calling user (sent is true)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestNotificationResponse" + } + } + } }, "400": { "$ref": "#/components/responses/BadRequest" @@ -2326,23 +2378,41 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, "404": { "$ref": "#/components/responses/NotFound" }, "500": { "$ref": "#/components/responses/InternalServerError" + }, + "502": { + "description": "The downstream notification service rejected or failed the test send.\nThe body uses TestNotificationResponse with sent set to false so the\noperator sees the real (failed) delivery outcome.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestNotificationResponse" + } + } + } } } } }, - "/v1/integrations/{integrationId}/outbound-status": { + "/v2/integrations/{integrationId}/notifications/status": { "get": { - "operationId": "getOutboundStatus", - "summary": "getOutboundStatus", - "description": "Get the status of all outbound use cases for a specific integration.\nReturns conflict information when events or webhooks are disabled but the use case is enabled.\n", + "operationId": "getNotificationStatus", + "summary": "getNotificationStatus", + "description": "Returns the live per-rule alert state and (for 'auto' rules) the current\nhour-of-week baseline band for an integration's notification monitoring.\nReflects the latest 5-minute sweep — near-real-time, not live.\nRequires the `integration:view` permission on the integration's organization.\n", "tags": [ "integrations" ], + "security": [ + { + "EpilotAuth": [] + } + ], "parameters": [ { "name": "integrationId", @@ -2353,22 +2423,40 @@ "type": "string", "format": "uuid" } + }, + { + "name": "include", + "in": "query", + "required": false, + "description": "Add `baseline_series` to also return all 168 hour-of-week buckets per\n'auto' rule (heavier; omit for just the current-bucket markers).\n", + "schema": { + "type": "string", + "enum": [ + "baseline_series" + ] + } } ], "responses": { "200": { - "description": "Successfully retrieved outbound status", + "description": "Live notification status", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OutboundStatusResponse" + "$ref": "#/components/schemas/NotificationStatusResponse" } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, "404": { "$ref": "#/components/responses/NotFound" }, @@ -2378,13 +2466,14 @@ } } }, - "/v1/integrations/{integrationId}/outbound/messages/poll": { - "post": { - "operationId": "pollOutboundMessages", - "summary": "pollOutboundMessages", - "description": "Poll outbound messages for an integration's poll-mode use cases.\nTakes a lease on the head-of-line batch of the integration's FIFO stream:\nthe returned messages stay invisible to subsequent polls until they are\nacknowledged or the visibility timeout elapses. POST because taking a\nlease mutates server state — auto-retrying middleware must not burn\nleases. One in-flight batch per stream: while a lease is active (or\nanother poll wins the race), the response is an empty batch\n(`messages: []`), not an error. Requires the `integration:consume` grant.\n", + "/v2/integrations/{integrationId}/use-cases/{useCaseId}/secure-proxy-whitelist": { + "get": { + "operationId": "getSecureProxyWhitelist", + "summary": "getSecureProxyWhitelist", + "description": "Returns the current allowed_domains, allowed_ips, and vpc_mode for a secure_proxy use case.\nStaff-only — gated by internal-auth issuer AND admin-portal Cognito user pool membership.\nRejects Login-As tokens.\n", "tags": [ - "integrations" + "integrations", + "proxy" ], "security": [ { @@ -2401,25 +2490,25 @@ "type": "string", "format": "uuid" } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PollOutboundMessagesRequest" - } + }, + { + "name": "useCaseId", + "in": "path", + "required": true, + "description": "The use case ID", + "schema": { + "type": "string", + "format": "uuid" } } - }, + ], "responses": { "200": { - "description": "Leased batch of outbound messages (possibly empty)", + "description": "Current whitelist", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PollOutboundMessagesResponse" + "$ref": "#/components/schemas/SecureProxyWhitelist" } } } @@ -2434,21 +2523,17 @@ "$ref": "#/components/responses/Forbidden" }, "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" + "description": "Use case not found or not of type secure_proxy" } } - } - }, - "/v1/integrations/{integrationId}/outbound/messages/ack": { - "post": { - "operationId": "ackOutboundMessages", - "summary": "ackOutboundMessages", - "description": "Acknowledge polled outbound messages. Acks are validated against the\nactive lease and committed as a prefix-contiguous cursor advance:\nmessages must be acknowledged in stream order. Out-of-order acks past\nthe first gap are rejected per id (`out_of_order`), acks with an\noutdated lease token are rejected (`stale_lease`), and unknown ids are\nrejected (`not_found`). Acknowledged messages are never delivered\nagain. Requires the `integration:consume` grant.\n", + }, + "put": { + "operationId": "updateSecureProxyWhitelist", + "summary": "updateSecureProxyWhitelist", + "description": "Replaces allowed_domains and/or allowed_ips on a secure_proxy use case.\nAt least one of the two fields is required. Validation mirrors the CLI's\n`validateDomainPatterns` / `validateCidrs`. Writes a USECASE_HISTORY row\nwith the admin user's email as `changed_by`.\n\nUpdate semantics per field:\n - **omitted** — field is not modified; the stored value is preserved.\n - **non-empty array** — the stored value is replaced with the supplied list.\n - **empty array (`[]`)** — the list is cleared (stored as `[]`). This is\n the canonical way to remove all entries. `null` is not accepted.\n\nStaff-only — same auth gates as GET.\n", "tags": [ - "integrations" + "integrations", + "proxy" ], "security": [ { @@ -2465,6 +2550,16 @@ "type": "string", "format": "uuid" } + }, + { + "name": "useCaseId", + "in": "path", + "required": true, + "description": "The use case ID", + "schema": { + "type": "string", + "format": "uuid" + } } ], "requestBody": { @@ -2472,18 +2567,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AckOutboundMessagesRequest" + "$ref": "#/components/schemas/SecureProxyWhitelistUpdate" } } } }, "responses": { "200": { - "description": "Per-id acknowledgement results", + "description": "Updated whitelist (same shape as GET)", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AckOutboundMessagesResponse" + "$ref": "#/components/schemas/SecureProxyWhitelist" } } } @@ -2498,21 +2593,19 @@ "$ref": "#/components/responses/Forbidden" }, "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" + "description": "Use case not found or not of type secure_proxy" } } } }, - "/v1/integrations/{integrationId}/outbound/messages/dlq": { + "/v2/integrations/{integrationId}/use-cases/{useCaseId}/secure-proxy-whitelist/history": { "get": { - "operationId": "listOutboundDlqMessages", - "summary": "listOutboundDlqMessages", - "description": "List an integration's dead-lettered outbound queue messages\n(poison_policy enforcement and operator skips move messages here).\nOperator endpoint — requires the `integration:manage` grant. Message\npayloads are NOT included in listings: entries are keyed by the\nopaque message id and carry delivery metadata only. Paginated via an\nopaque `next_token`.\n", + "operationId": "listSecureProxyWhitelistHistory", + "summary": "listSecureProxyWhitelistHistory", + "description": "Returns the most recent USECASE_HISTORY entries for a secure_proxy use case,\nin reverse chronological order (newest first). Each entry includes the\nactor email (`changed_by`), the ISO-8601 timestamp (`history_created_at`),\nthe `change_description` (free-text action), and the full `configuration`\nsnapshot — from which UI-08 computes a before/after diff between consecutive\nentries.\n\nStaff-only — gated by internal-auth issuer AND admin-portal Cognito user pool\nmembership. Rejects Login-As tokens (same auth gate as the GET / PUT\nsecure-proxy-whitelist operations).\n\nThin wrapper over the service-layer `listUseCaseHistory` that powers\n`GET /v1/integrations/{integrationId}/use-cases/{useCaseId}/history` — the\nseparate path exists because the /v1 variant is tenant-gated and admin-portal\ninternal-auth tokens do not carry tenant permissions.\n", "tags": [ - "integrations" + "integrations", + "proxy" ], "security": [ { @@ -2531,34 +2624,47 @@ } }, { - "name": "limit", - "in": "query", - "required": false, - "description": "Maximum number of DLQ entries to return", + "name": "useCaseId", + "in": "path", + "required": true, + "description": "The use case ID (must be of type secure_proxy)", "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 + "type": "string", + "format": "uuid" } }, { - "name": "next_token", + "name": "limit", "in": "query", "required": false, - "description": "Opaque pagination token from a previous response", + "description": "Maximum number of history entries to return. Default 10, max 50.\nCapped at the service-layer page size (20) so `limit > 20` is silently\nclamped to 20. UI-08 requests 5-10 for the panel view.\n", "schema": { - "type": "string" + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 10 } } ], "responses": { "200": { - "description": "Page of dead-lettered messages", + "description": "Successfully retrieved secure_proxy history", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OutboundDlqListResponse" + "type": "object", + "required": [ + "history" + ], + "properties": { + "history": { + "type": "array", + "description": "Entries are `SecureProxyUseCaseHistoryEntry` in reverse\nchronological order (newest first). Entries older than the\nrequested `limit` are not returned.\n", + "items": { + "$ref": "#/components/schemas/SecureProxyUseCaseHistoryEntry" + } + } + } } } } @@ -2573,7 +2679,7 @@ "$ref": "#/components/responses/Forbidden" }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Use case not found or not of type secure_proxy" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -2581,25 +2687,20 @@ } } }, - "/v1/integrations/{integrationId}/outbound/messages/dlq/redrive": { - "post": { - "operationId": "redriveOutboundDlqMessages", - "summary": "redriveOutboundDlqMessages", - "description": "Redrive selected dead-lettered messages back into the live stream.\nOperator endpoint — requires the `integration:manage` grant.\nA redriven message is re-enqueued at the tail with a new id and\nsequence — it is delivered out of its original per-entity order (the\nstream has moved on); this is inherent to redrive and matches SQS DLQ\nsemantics. The redriven copy starts with zero delivery attempts and a\nfresh retention window; the original DLQ entry is removed. Per-id\nresults report `redriven` or `not_found` (unknown ids, or entries\nconcurrently redriven/expired).\n", + "/v1/integrations/{integrationId}/app-mapping": { + "put": { + "operationId": "setIntegrationAppMapping", + "summary": "setIntegrationAppMapping", + "description": "Creates or updates a mapping from an app/component to an integration.\nThis allows ERP updates sent via app_id and component_id to be associated\nwith a specific integration configuration.\n", "tags": [ "integrations" ], - "security": [ - { - "EpilotAuth": [] - } - ], "parameters": [ { "name": "integrationId", "in": "path", "required": true, - "description": "The integration ID", + "description": "The integration ID to map to", "schema": { "type": "string", "format": "uuid" @@ -2611,18 +2712,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RedriveOutboundDlqRequest" + "$ref": "#/components/schemas/SetIntegrationAppMappingRequest" } } } }, "responses": { "200": { - "description": "Per-id redrive results", + "description": "App mapping set successfully", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RedriveOutboundDlqResponse" + "$ref": "#/components/schemas/IntegrationAppMapping" } } } @@ -2633,89 +2734,29 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "Integration not found" }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/integrations/{integrationId}/outbound/messages/unblock": { - "post": { - "operationId": "unblockOutboundStream", - "summary": "unblockOutboundStream", - "description": "Unblock an integration's outbound stream halted by the `block`\npoison policy: skips (dead-letters) the current blocked head message,\nemitting MSG_DEAD_LETTERED and letting the next message become the\nhead. Operator endpoint — requires the `integration:manage` grant.\nReturns `unblocked: false` as a no-op when the stream is not\ncurrently blocked (or the state moved concurrently) — safe to retry.\n", - "tags": [ - "integrations" - ], - "security": [ - { - "EpilotAuth": [] - } - ], - "parameters": [ - { - "name": "integrationId", - "in": "path", - "required": true, - "description": "The integration ID", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "required": false, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnblockOutboundStreamRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Unblock outcome", + "409": { + "description": "Mapping already exists (use overwrite=true to replace)", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnblockOutboundStreamResponse" + "$ref": "#/components/schemas/ErrorResponseBase" } } } }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, "500": { "$ref": "#/components/responses/InternalServerError" } } - } - }, - "/v1/integrations/{integrationId}/monitoring/access-logs": { - "post": { - "operationId": "queryAccessLogs", - "summary": "queryAccessLogs", - "description": "Query API access logs for a specific integration's organization.\nReturns access token usage analytics filtered by user_id (access token).\nSupports infinite scroll pagination with cursor-based navigation.\n", + }, + "delete": { + "operationId": "deleteIntegrationAppMapping", + "summary": "deleteIntegrationAppMapping", + "description": "Removes a mapping from an app/component to an integration.\n", "tags": [ - "monitoring", "integrations" ], "parameters": [ @@ -2723,7 +2764,7 @@ "name": "integrationId", "in": "path", "required": true, - "description": "The integration ID (used for tenant authorization)", + "description": "The integration ID (used for authorization, must match the mapping)", "schema": { "type": "string", "format": "uuid" @@ -2735,14 +2776,26 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/QueryAccessLogsRequest" + "$ref": "#/components/schemas/DeleteIntegrationAppMappingRequest" } } } }, "responses": { "200": { - "$ref": "#/components/responses/QueryAccessLogsResponse" + "description": "App mapping deleted successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } }, "400": { "$ref": "#/components/responses/BadRequest" @@ -2751,7 +2804,7 @@ "$ref": "#/components/responses/Unauthorized" }, "404": { - "$ref": "#/components/responses/NotFound" + "description": "App mapping not found" }, "500": { "$ref": "#/components/responses/InternalServerError" @@ -2759,11 +2812,11 @@ } } }, - "/v1/integrations/{integrationId}/monitoring/outbound-events": { + "/v1/integrations/{integrationId}/monitoring/inbound-events": { "post": { - "operationId": "queryOutboundMonitoringEvents", - "summary": "queryOutboundMonitoringEvents", - "description": "Query outbound monitoring events for a specific integration.\nReturns detailed information about outbound event deliveries,\nfiltered by event_name (event_catalog_event) linked to the integration's outbound use cases.\n", + "operationId": "queryInboundMonitoringEvents", + "summary": "queryInboundMonitoringEvents", + "description": "Query inbound monitoring events for a specific integration.\nReturns detailed information about inbound sync events from ERP systems,\nincluding success rates, error breakdowns, and processing metrics.\n", "tags": [ "monitoring", "integrations" @@ -2785,14 +2838,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/QueryOutboundMonitoringEventsRequest" + "$ref": "#/components/schemas/QueryInboundMonitoringEventsRequest" } } } }, "responses": { "200": { - "$ref": "#/components/responses/QueryOutboundMonitoringEventsResponse" + "$ref": "#/components/responses/QueryInboundMonitoringEventsResponse" }, "400": { "$ref": "#/components/responses/BadRequest" @@ -2809,11 +2862,11 @@ } } }, - "/v2/integrations/{integrationId}/monitoring/events": { + "/v1/integrations/{integrationId}/monitoring/stats": { "post": { - "operationId": "queryMonitoringEventsV2", - "summary": "queryMonitoringEventsV2", - "description": "Query monitoring events from the unified erp_monitoring_v2 table.\nReturns all event types (inbound, outbound, file_proxy, etc.) in a single list.\nReplaces the separate v1 inbound-events and outbound-events endpoints.\n", + "operationId": "getMonitoringStats", + "summary": "getMonitoringStats", + "description": "Get aggregated statistics for both inbound and outbound monitoring events for a specific integration.\nReturns summary metrics for inbound (ERP sync) and outbound (webhook delivery) events,\nincluding success/error counts and optional breakdowns.\n", "tags": [ "monitoring", "integrations" @@ -2835,14 +2888,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/QueryMonitoringEventsV2Request" + "$ref": "#/components/schemas/GetMonitoringStatsRequest" } } } }, "responses": { "200": { - "$ref": "#/components/responses/QueryMonitoringEventsV2Response" + "$ref": "#/components/responses/GetMonitoringStatsResponse" }, "400": { "$ref": "#/components/responses/BadRequest" @@ -2859,11 +2912,11 @@ } } }, - "/v2/integrations/{integrationId}/monitoring/stats": { + "/v1/integrations/{integrationId}/monitoring/timeseries": { "post": { - "operationId": "getMonitoringStatsV2", - "summary": "getMonitoringStatsV2", - "description": "Get aggregated statistics from the unified erp_monitoring_v2 table.\nReturns combined metrics for all event types with optional breakdowns.\n", + "operationId": "getMonitoringTimeSeries", + "summary": "getMonitoringTimeSeries", + "description": "Get time-series aggregated event counts for monitoring charts.\nReturns pre-bucketed counts at configurable intervals for both inbound and outbound events.\nMaximum of 200 buckets per request. Returns 400 if the time range and interval would exceed this limit.\n", "tags": [ "monitoring", "integrations" @@ -2885,14 +2938,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetMonitoringStatsV2Request" + "$ref": "#/components/schemas/GetMonitoringTimeSeriesRequest" } } } }, "responses": { "200": { - "$ref": "#/components/responses/GetMonitoringStatsV2Response" + "$ref": "#/components/responses/GetMonitoringTimeSeriesResponse" }, "400": { "$ref": "#/components/responses/BadRequest" @@ -2909,13 +2962,12 @@ } } }, - "/v2/integrations/{integrationId}/monitoring/time-series": { - "post": { - "operationId": "getMonitoringTimeSeriesV2", - "summary": "getMonitoringTimeSeriesV2", - "description": "Get time-series aggregated event counts from the unified erp_monitoring_v2 table.\nReturns bucketed counts for chart rendering.\n", + "/v1/integrations/{integrationId}/outbound-status": { + "get": { + "operationId": "getOutboundStatus", + "summary": "getOutboundStatus", + "description": "Get the status of all outbound use cases for a specific integration.\nReturns conflict information when events or webhooks are disabled but the use case is enabled.\n", "tags": [ - "monitoring", "integrations" ], "parameters": [ @@ -2930,22 +2982,16 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetMonitoringTimeSeriesV2Request" - } - } - } - }, "responses": { "200": { - "$ref": "#/components/responses/GetMonitoringTimeSeriesV2Response" - }, - "400": { - "$ref": "#/components/responses/BadRequest" + "description": "Successfully retrieved outbound status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OutboundStatusResponse" + } + } + } }, "401": { "$ref": "#/components/responses/Unauthorized" @@ -2959,39 +3005,46 @@ } } }, - "/v2/integrations/{integrationId}/monitoring/events/{eventId}/associated": { + "/v1/entities/{entityId}/sync-status": { "get": { - "operationId": "getAssociatedMonitoringEvents", - "summary": "getAssociatedMonitoringEvents", - "description": "Returns all monitoring events sharing the same event_id, ordered chronologically.\nAlso includes the original inbound event payload from erp_incoming_events if available.\nUsed to display a full event trace/timeline.\n", + "operationId": "getEntitySyncStatus", + "summary": "getEntitySyncStatus", + "description": "Get the inbound ERP sync status of an entity: when each integration last\nsynchronized (checked) the entity against the ERP, and when it last\nactually changed it. `last_synced_at` also advances on no-op checks —\nevents that were received and evaluated but changed nothing — which by\ndesign leave no trace on the entity itself (no activity feed entry, no\n`_updated_at` bump). Use it to tell whether an entity is up to date with\nthe ERP. Returns an empty list for entities no inbound use case has\nprocessed. Org-scoped via the caller's token.\n", "tags": [ - "monitoring", "integrations" ], "parameters": [ { - "name": "integrationId", + "name": "entityId", "in": "path", "required": true, - "description": "The integration ID", + "description": "The entity ID", "schema": { "type": "string", "format": "uuid" } }, { - "name": "eventId", - "in": "path", - "required": true, - "description": "The event ID to get associated events for", + "name": "integration_id", + "in": "query", + "required": false, + "description": "Narrow the result to a single integration", "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], "responses": { "200": { - "$ref": "#/components/responses/GetAssociatedMonitoringEventsResponse" + "description": "Successfully retrieved entity sync status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntitySyncStatusResponse" + } + } + } }, "400": { "$ref": "#/components/responses/BadRequest" @@ -3005,11 +3058,11 @@ } } }, - "/v1/integrations/secure-proxies": { - "get": { - "operationId": "listSecureProxies", - "summary": "List all secure proxy use cases", - "description": "Lists all secure_proxy use cases across all integrations for the authenticated organization.\nReturns minimal data suitable for dropdowns and selection UIs.\n", + "/v1/integrations/{integrationId}/outbound/messages/poll": { + "post": { + "operationId": "pollOutboundMessages", + "summary": "pollOutboundMessages", + "description": "Poll outbound messages for an integration's poll-mode use cases.\nTakes a lease on the head-of-line batch of the integration's FIFO stream:\nthe returned messages stay invisible to subsequent polls until they are\nacknowledged or the visibility timeout elapses. POST because taking a\nlease mutates server state — auto-retrying middleware must not burn\nleases. One in-flight batch per stream: while a lease is active (or\nanother poll wins the race), the response is an empty batch\n(`messages: []`), not an error. Requires the `integration:consume` grant.\n", "tags": [ "integrations" ], @@ -3018,67 +3071,99 @@ "EpilotAuth": [] } ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PollOutboundMessagesRequest" + } + } + } + }, "responses": { "200": { - "description": "Successfully retrieved secure proxy use cases", + "description": "Leased batch of outbound messages (possibly empty)", "content": { "application/json": { "schema": { - "type": "object", - "required": [ - "secure_proxies" - ], - "properties": { - "secure_proxies": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SecureProxySummary" - } - } - } + "$ref": "#/components/schemas/PollOutboundMessagesResponse" } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, "500": { "$ref": "#/components/responses/InternalServerError" } } } }, - "/v1/secure-proxy": { + "/v1/integrations/{integrationId}/outbound/messages/ack": { "post": { - "operationId": "secureProxy", - "summary": "Proxy HTTP request through secure VPC", - "description": "Routes an HTTP request through a VPC with either static IP egress or VPN secure link access.\nThe VPC mode is determined by the referenced secure_proxy use case configuration.\nFor secure_link mode, the target URL must match the use case's allowed_domains whitelist.\n", + "operationId": "ackOutboundMessages", + "summary": "ackOutboundMessages", + "description": "Acknowledge polled outbound messages. Acks are validated against the\nactive lease and committed as a prefix-contiguous cursor advance:\nmessages must be acknowledged in stream order. Out-of-order acks past\nthe first gap are rejected per id (`out_of_order`), acks with an\noutdated lease token are rejected (`stale_lease`), and unknown ids are\nrejected (`not_found`). Acknowledged messages are never delivered\nagain. Requires the `integration:consume` grant.\n", "tags": [ - "proxy" + "integrations" ], "security": [ { "EpilotAuth": [] } ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SecureProxyRequest" + "$ref": "#/components/schemas/AckOutboundMessagesRequest" } } } }, "responses": { "200": { - "description": "Proxied response from the target URL", + "description": "Per-id acknowledgement results", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SecureProxyResponse" + "$ref": "#/components/schemas/AckOutboundMessagesResponse" } } } @@ -3090,55 +3175,99 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "description": "Forbidden - insufficient permissions or target domain not whitelisted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponseBase" - } - } - } + "$ref": "#/components/responses/Forbidden" }, - "502": { - "description": "Upstream error from the proxied target", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponseBase" - } - } + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/integrations/{integrationId}/outbound/messages/dlq": { + "get": { + "operationId": "listOutboundDlqMessages", + "summary": "listOutboundDlqMessages", + "description": "List an integration's dead-lettered outbound queue messages\n(poison_policy enforcement and operator skips move messages here).\nOperator endpoint — requires the `integration:manage` grant. Message\npayloads are NOT included in listings: entries are keyed by the\nopaque message id and carry delivery metadata only. Paginated via an\nopaque `next_token`.\n", + "tags": [ + "integrations" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" } }, - "503": { - "description": "VPC proxy not available for the requested mode", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponseBase" - } - } + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of DLQ entries to return", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 } }, - "504": { - "description": "Upstream timeout", + { + "name": "next_token", + "in": "query", + "required": false, + "description": "Opaque pagination token from a previous response", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Page of dead-lettered messages", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponseBase" + "$ref": "#/components/schemas/OutboundDlqListResponse" } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } } } }, - "/v1/managed-call/{slug}/execute": { + "/v1/integrations/{integrationId}/outbound/messages/dlq/redrive": { "post": { - "operationId": "managedCallExecute", - "summary": "Execute a managed call operation", - "description": "Execute a managed call operation synchronously. The slug in the path acts as the RPC method name.\nCalls an external partner API with JSONata mapping on both request and response.\n", + "operationId": "redriveOutboundDlqMessages", + "summary": "redriveOutboundDlqMessages", + "description": "Redrive selected dead-lettered messages back into the live stream.\nOperator endpoint — requires the `integration:manage` grant.\nA redriven message is re-enqueued at the tail with a new id and\nsequence — it is delivered out of its original per-entity order (the\nstream has moved on); this is inherent to redrive and matches SQS DLQ\nsemantics. The redriven copy starts with zero delivery attempts and a\nfresh retention window; the original DLQ entry is removed. Per-id\nresults report `redriven` or `not_found` (unknown ids, or entries\nconcurrently redriven/expired).\n", "tags": [ - "managed-call" + "integrations" ], "security": [ { @@ -3147,14 +3276,14 @@ ], "parameters": [ { - "name": "slug", + "name": "integrationId", "in": "path", "required": true, + "description": "The integration ID", "schema": { "type": "string", - "pattern": "^[a-z0-9][a-z0-9_-]*$" - }, - "description": "Use case slug (acts as the RPC method name)" + "format": "uuid" + } } ], "requestBody": { @@ -3162,39 +3291,24 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ManagedCallExecuteRequest" + "$ref": "#/components/schemas/RedriveOutboundDlqRequest" } } } }, "responses": { "200": { - "description": "Managed call executed successfully.\nThe response body is the JSONata-mapped result (or raw external API response if no mapping).\nIf inbound routing is configured, check X-Inbound-Event-Id header for tracking.\n", - "headers": { - "X-Inbound-Event-Id": { - "description": "Event ID for tracking inbound pipeline processing (only present when inbound routing is configured)", - "schema": { - "type": "string" - } - } - }, + "description": "Per-id redrive results", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ManagedCallExecuteResponse" + "$ref": "#/components/schemas/RedriveOutboundDlqResponse" } } } }, "400": { - "description": "Bad request (e.g., invalid URL blocked by SSRF protection)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ManagedCallErrorResponse" - } - } - } + "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" @@ -3207,25 +3321,15 @@ }, "500": { "$ref": "#/components/responses/InternalServerError" - }, - "502": { - "description": "External API error (upstream service returned an error)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ManagedCallErrorResponse" - } - } - } } } } }, - "/v1/integrations/{integrationId}/generate-types-preview": { + "/v1/integrations/{integrationId}/outbound/messages/unblock": { "post": { - "operationId": "generateTypesPreview", - "summary": "Preview scaffolded types for a connector integration", - "description": "Analyses the JSONata mappings of all managed-call use cases in the integration and returns scaffolded type descriptors. The frontend uses these to show the type editor modal where developers fill in leaf types.\n", + "operationId": "unblockOutboundStream", + "summary": "unblockOutboundStream", + "description": "Unblock an integration's outbound stream halted by the `block`\npoison policy: skips (dead-letters) the current blocked head message,\nemitting MSG_DEAD_LETTERED and letting the next message become the\nhead. Operator endpoint — requires the `integration:manage` grant.\nReturns `unblocked: false` as a no-op when the stream is not\ncurrently blocked (or the state moved concurrently) — safe to retry.\n", "tags": [ "integrations" ], @@ -3239,19 +3343,30 @@ "name": "integrationId", "in": "path", "required": true, + "description": "The integration ID", "schema": { "type": "string", "format": "uuid" } } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnblockOutboundStreamRequest" + } + } + } + }, "responses": { "200": { - "description": "Scaffolded type preview", + "description": "Unblock outcome", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GenerateTypesPreviewResponse" + "$ref": "#/components/schemas/UnblockOutboundStreamResponse" } } } @@ -3259,6 +3374,9 @@ "400": { "$ref": "#/components/responses/BadRequest" }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, "403": { "$ref": "#/components/responses/Forbidden" }, @@ -3271,24 +3389,21 @@ } } }, - "/v1/integrations/{integrationId}/generate-types": { + "/v1/integrations/{integrationId}/monitoring/access-logs": { "post": { - "operationId": "generateTypes", - "summary": "Generate a TypeScript npm package for a connector integration", - "description": "Generates a complete TypeScript npm package with typed interfaces for all managed-call use cases. This is a stateless operation that does not persist any changes. Use the commit-types endpoint to lock configurations after review.\n", + "operationId": "queryAccessLogs", + "summary": "queryAccessLogs", + "description": "Query API access logs for a specific integration's organization.\nReturns access token usage analytics filtered by user_id (access token).\nSupports infinite scroll pagination with cursor-based navigation.\n", "tags": [ + "monitoring", "integrations" ], - "security": [ - { - "EpilotAuth": [] - } - ], "parameters": [ { "name": "integrationId", "in": "path", "required": true, + "description": "The integration ID (used for tenant authorization)", "schema": { "type": "string", "format": "uuid" @@ -3300,27 +3415,20 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GenerateTypesRequest" + "$ref": "#/components/schemas/QueryAccessLogsRequest" } } } }, "responses": { "200": { - "description": "Generated package", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenerateTypesResponse" - } - } - } + "$ref": "#/components/responses/QueryAccessLogsResponse" }, "400": { "$ref": "#/components/responses/BadRequest" }, - "403": { - "$ref": "#/components/responses/Forbidden" + "401": { + "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" @@ -3331,24 +3439,21 @@ } } }, - "/v1/integrations/{integrationId}/commit-types": { + "/v1/integrations/{integrationId}/monitoring/outbound-events": { "post": { - "operationId": "commitTypes", - "summary": "Commit generated types and lock use case configurations", - "description": "Commits the generated types by locking use case configurations and updating version tracking. Should be called after the user reviews and downloads the generated package.\n", + "operationId": "queryOutboundMonitoringEvents", + "summary": "queryOutboundMonitoringEvents", + "description": "Query outbound monitoring events for a specific integration.\nReturns detailed information about outbound event deliveries,\nfiltered by event_name (event_catalog_event) linked to the integration's outbound use cases.\n", "tags": [ + "monitoring", "integrations" ], - "security": [ - { - "EpilotAuth": [] - } - ], "parameters": [ { "name": "integrationId", "in": "path", "required": true, + "description": "The integration ID", "schema": { "type": "string", "format": "uuid" @@ -3360,27 +3465,20 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CommitTypesRequest" + "$ref": "#/components/schemas/QueryOutboundMonitoringEventsRequest" } } } }, "responses": { "200": { - "description": "Types committed successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CommitTypesResponse" - } - } - } + "$ref": "#/components/responses/QueryOutboundMonitoringEventsResponse" }, "400": { "$ref": "#/components/responses/BadRequest" }, - "403": { - "$ref": "#/components/responses/Forbidden" + "401": { + "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" @@ -3390,31 +3488,1840 @@ } } } - } - }, - "components": { - "securitySchemes": { - "EpilotAuth": { - "type": "http", - "scheme": "bearer", - "description": "Authorization header with epilot OAuth2 bearer token", - "bearerFormat": "JWT" - } }, - "schemas": { - "ErrorResponseBase": { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Computer-readable error code" - }, - "message": { - "type": "string", - "description": "Error message" - } - } - }, + "/v2/integrations/{integrationId}/monitoring/events": { + "post": { + "operationId": "queryMonitoringEventsV2", + "summary": "queryMonitoringEventsV2", + "description": "Query monitoring events from the unified erp_monitoring_v2 table.\nReturns all event types (inbound, outbound, file_proxy, etc.) in a single list.\nReplaces the separate v1 inbound-events and outbound-events endpoints.\n", + "tags": [ + "monitoring", + "integrations" + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueryMonitoringEventsV2Request" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/QueryMonitoringEventsV2Response" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/integrations/{integrationId}/monitoring/stats": { + "post": { + "operationId": "getMonitoringStatsV2", + "summary": "getMonitoringStatsV2", + "description": "Get aggregated statistics from the unified erp_monitoring_v2 table.\nReturns combined metrics for all event types with optional breakdowns.\n", + "tags": [ + "monitoring", + "integrations" + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMonitoringStatsV2Request" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/GetMonitoringStatsV2Response" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/integrations/{integrationId}/monitoring/time-series": { + "post": { + "operationId": "getMonitoringTimeSeriesV2", + "summary": "getMonitoringTimeSeriesV2", + "description": "Get time-series aggregated event counts from the unified erp_monitoring_v2 table.\nReturns bucketed counts for chart rendering.\n", + "tags": [ + "monitoring", + "integrations" + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMonitoringTimeSeriesV2Request" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/GetMonitoringTimeSeriesV2Response" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/integrations/{integrationId}/monitoring/events/{eventId}/associated": { + "get": { + "operationId": "getAssociatedMonitoringEvents", + "summary": "getAssociatedMonitoringEvents", + "description": "Returns all monitoring events sharing the same event_id, ordered chronologically.\nAlso includes the original inbound event payload from erp_incoming_events if available.\nUsed to display a full event trace/timeline.\n", + "tags": [ + "monitoring", + "integrations" + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "eventId", + "in": "path", + "required": true, + "description": "The event ID to get associated events for", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/GetAssociatedMonitoringEventsResponse" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/integrations/{integrationId}/monitoring/external-events": { + "post": { + "operationId": "ingestExternalMonitoringEvents", + "summary": "ingestExternalMonitoringEvents", + "description": "Ingest monitoring spans produced by an EXTERNAL system (e.g. an integration\nmiddleware), so the Integration Hub is the central monitoring point and the\ncross-system event trace spans both the external system and epilot's own\nprocessing.\n\nEach span is validated, assigned a server-controlled `EXTERNAL_*` taxonomy code\nderived from its `level` (the client never supplies a code — this marks\nprovenance and prevents spoofing an epilot code), has its `use_case_slug`\nresolved against the integration's configured use cases, and is republished onto\nthe same monitoring event bus as epilot's own spans — so external spans are\nfirst-class to the trace view, stats, alerting and digests.\n\nSpans link to the epilot trace via `correlation_id` (the trace id): the middleware\nmust stamp the same `correlation_id` here and on the event it forwards to the\ninbound endpoint. Invalid spans in a batch are reported per-item and do not fail\nthe whole batch.\n", + "tags": [ + "monitoring", + "integrations" + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestExternalMonitoringEventsRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Batch accepted (some spans may have been rejected — see counts).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestExternalMonitoringEventsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/integrations/{integrationId}/monitoring/traces/{correlationId}": { + "get": { + "operationId": "getMonitoringTraceByCorrelation", + "summary": "getMonitoringTraceByCorrelation", + "description": "Returns the cross-system event trace for a `correlation_id`: every monitoring\nspan sharing it — external spans (middleware) plus epilot's own processing spans\n— ordered chronologically, with a rolled-up status and the epilot inbound event\n\"head\" attached. This is the correlation-grouped counterpart to\n`…/events/{eventId}/associated` (which groups a single epilot event's fan-out by\nevent_id). Origin is distinguished by the `EXTERNAL_*` code prefix on external\nspans. Freshness is near-real-time (spans land as they are produced/ingested).\n", + "tags": [ + "monitoring", + "integrations" + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "description": "The integration ID", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "correlationId", + "in": "path", + "required": true, + "description": "The trace id (correlation_id) shared across systems", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The assembled cross-system trace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringTraceResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/integrations/secure-proxies": { + "get": { + "operationId": "listSecureProxies", + "summary": "listSecureProxies", + "description": "Lists all secure_proxy use cases across all integrations for the authenticated organization.\nReturns minimal data suitable for dropdowns and selection UIs.\n", + "tags": [ + "integrations" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "responses": { + "200": { + "description": "Successfully retrieved secure proxy use cases", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "secure_proxies" + ], + "properties": { + "secure_proxies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SecureProxySummary" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/secure-proxy": { + "post": { + "operationId": "secureProxy", + "summary": "secureProxy", + "description": "Routes an HTTP request through a VPC with either static IP egress or VPN secure link access.\nThe VPC mode is determined by the referenced secure_proxy use case configuration.\nFor secure_link mode, the target URL must match the use case's allowed_domains whitelist.\n", + "tags": [ + "proxy" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SecureProxyRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Proxied response from the target URL", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SecureProxyResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "description": "Forbidden - insufficient permissions or target domain not whitelisted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBase" + } + } + } + }, + "502": { + "description": "Upstream failure: epilot could not obtain an HTTP response from the proxied target\n(e.g. TLS handshake failure, connection refused/reset, DNS failure or timeout).\nThe body's `code`/`reason` identify the underlying cause, distinguishing a\nremote-side problem from an epilot-side one. When the target *does* answer with\nits own status, that status and body are passed through unchanged via\n`SecureProxyResponse` instead.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SecureProxyUpstreamError" + } + } + } + }, + "503": { + "description": "VPC proxy not available for the requested mode", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBase" + } + } + } + }, + "504": { + "description": "Upstream timeout", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBase" + } + } + } + } + } + } + }, + "/v1/managed-call/{slug}/execute": { + "post": { + "operationId": "managedCallExecute", + "summary": "managedCallExecute", + "description": "Execute a managed call operation synchronously. The slug in the path acts as the RPC method name.\nCalls an external partner API with JSONata mapping on both request and response.\n", + "tags": [ + "managed-call" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_-]*$" + }, + "description": "Use case slug (acts as the RPC method name)" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagedCallExecuteRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Managed call executed successfully.\nThe response body is the JSONata-mapped result (or raw external API response if no mapping).\nIf inbound routing is configured, check X-Inbound-Event-Id header for tracking.\n", + "headers": { + "X-Inbound-Event-Id": { + "description": "Event ID for tracking inbound pipeline processing (only present when inbound routing is configured)", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagedCallExecuteResponse" + } + } + } + }, + "400": { + "description": "Bad request (e.g., invalid URL blocked by SSRF protection)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagedCallErrorResponse" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + }, + "502": { + "description": "External API error (upstream service returned an error)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagedCallErrorResponse" + } + } + } + } + } + } + }, + "/v1/integrations/{integrationId}/generate-types-preview": { + "post": { + "operationId": "generateTypesPreview", + "summary": "generateTypesPreview", + "description": "Analyses the JSONata mappings of all managed-call use cases in the integration and returns scaffolded type descriptors. The frontend uses these to show the type editor modal where developers fill in leaf types.\n", + "tags": [ + "integrations" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Scaffolded type preview", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateTypesPreviewResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/integrations/{integrationId}/generate-types": { + "post": { + "operationId": "generateTypes", + "summary": "generateTypes", + "description": "Generates a complete TypeScript npm package with typed interfaces for all managed-call use cases. This is a stateless operation that does not persist any changes. Use the commit-types endpoint to lock configurations after review.\n", + "tags": [ + "integrations" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateTypesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Generated package", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateTypesResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v1/integrations/{integrationId}/commit-types": { + "post": { + "operationId": "commitTypes", + "summary": "commitTypes", + "description": "Commits the generated types by locking use case configurations and updating version tracking. Should be called after the user reviews and downloads the generated package.\n", + "tags": [ + "integrations" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CommitTypesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Types committed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CommitTypesResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/erp/imports": { + "post": { + "operationId": "createErpImport", + "summary": "createErpImport", + "description": "Register an already-uploaded file (S3 ref) as a pricing-file import job. Returns the job and a file preview. Nothing runs yet: no use case is chosen and no validation starts here. Optionally rank candidates with POST /v2/erp/imports/{importId}:suggest-use-cases, then start validation with POST /v2/erp/imports/{importId}:validate.\nPass `import_id` to repoint an existing PENDING import at a different file instead, keeping its id and its place in the history.", + "tags": [ + "erp-imports" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateErpImportRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The named import now points at this file. Returned only when `import_id` was supplied — no `Location`, because nothing was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateErpImportResponse" + } + } + } + }, + "201": { + "description": "Import job created. Returned only when `import_id` was omitted.", + "headers": { + "Location": { + "description": "URL of the created job (`/v2/erp/imports/{importId}`).", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateErpImportResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "get": { + "operationId": "listErpImports", + "summary": "listErpImports", + "description": "List recent pricing-file import jobs for the org, newest first.\n\nPass `next_cursor` back as `cursor` for the next page. A page can be\nshorter than `limit` and still have more behind it, so stop on\n`next_cursor: null`.\n", + "tags": [ + "erp-imports" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Values above 100 are clamped to 100.", + "schema": { + "type": "integer", + "minimum": 1, + "default": 50 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "description": "Opaque cursor from a prior page's `next_cursor`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Import jobs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErpImportList" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/erp/imports/{importId}": { + "get": { + "operationId": "getErpImport", + "summary": "getErpImport", + "description": "Get a pricing-file import job (status, counts, result links).", + "tags": [ + "erp-imports" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The import job", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErpImportJob" + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "delete": { + "operationId": "deleteErpImport", + "summary": "deleteErpImport", + "description": "Remove an import and the file it owns. Allowed from any status: an import whose run is still in flight is stopped by the deletion, and rows it already wrote stay written.", + "tags": [ + "erp-imports" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The import and its file were removed" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/erp/imports/{importId}:validate": { + "post": { + "operationId": "validateErpImport", + "summary": "validateErpImport", + "description": "Choose the use case to read this file with, and start the validate phase.\nCallable from PENDING, READY and FAILED — so a wrong choice is corrected by calling this again with a different use case, rather than re-uploading. Each call replaces any previous verdict. Any other status returns 409.", + "tags": [ + "erp-imports" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateErpImportRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Validation started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErpImportJob" + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "description": "The use case cannot drive an import", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBase" + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/erp/imports/{importId}:suggest-use-cases": { + "post": { + "operationId": "suggestErpImportUseCases", + "summary": "suggestErpImportUseCases", + "description": "Rank the org's inbound use cases against this file's columns — the input to the ranked picker (\"matches 6 of your 7 columns\"). Optional: skip this and call `:validate` directly when the use case is already known. Reads only the file's first row, not its data.\nEvery eligible use case is returned, including ones matching nothing: the \"nothing fits, pick anyway\" view needs the full list. Highest match first.\nCSV only — an xlsx import fails with 400 rather than silently returning zero matches, which would look identical to \"we checked and nothing matches\".", + "tags": [ + "erp-imports" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ranked use-case suggestions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuggestErpImportUseCasesResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/erp/imports/{importId}:execute": { + "post": { + "operationId": "executeErpImport", + "summary": "executeErpImport", + "description": "Confirm and run the write phase of a validated import. Only a READY job may be executed; any other status returns 409.", + "tags": [ + "erp-imports" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecuteErpImportRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Execution started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErpImportJob" + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "description": "The verdict carries warnings and `ack_warnings` was not true", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBase" + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/v2/erp/imports/{importId}:abort": { + "post": { + "operationId": "abortErpImport", + "summary": "abortErpImport", + "description": "Ask a running import to stop. Valid while the job is VALIDATING or PROCESSING; any other status returns 409.\nThe stop is cooperative: the job passes through CANCELLING and typically reaches CANCELLED within seconds.\nRows already published to the platform are **kept**: this is a stop, not a rollback. Aborting during VALIDATING has published nothing, so it is always clean; aborting during PROCESSING leaves a partial import, and the rows that landed are visible in monitoring under the job's correlation_id.", + "tags": [ + "erp-imports" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "202": { + "description": "The stop was recorded. The job is still finishing up — poll GET /v2/erp/imports/{importId} to observe CANCELLED.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErpImportJob" + } + } + } + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + } + }, + "components": { + "securitySchemes": { + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "description": "Authorization header with epilot OAuth2 bearer token", + "bearerFormat": "JWT" + } + }, + "schemas": { + "S3Reference": { + "type": "object", + "required": [ + "bucket", + "key" + ], + "properties": { + "bucket": { + "type": "string" + }, + "key": { + "type": "string" + } + } + }, + "CreateErpImportRequest": { + "type": "object", + "description": "Register an already-uploaded file as an import. The use case is chosen later, via `:validate` — upload and interpretation are separate decisions.", + "required": [ + "s3_reference" + ], + "additionalProperties": false, + "properties": { + "s3_reference": { + "$ref": "#/components/schemas/S3Reference" + }, + "include_preview": { + "type": "boolean", + "default": false, + "description": "Return a sample of the file's first rows in `preview`. Only controls whether the sample comes BACK — the head is read either way, because that read is how an unreadable file gets refused." + }, + "import_id": { + "type": "string", + "description": "Repoint an EXISTING import at this file instead of registering a new one — same row, same id, so iterating on which file to import leaves one entry in the history rather than one per attempt.\nOnly a PENDING import may be repointed; any other status returns 409 with code `IMPORT_NOT_REPLACEABLE`. Past the first check the previous verdict is what tells the user what to fix, and a job that reached the execute phase may already have published rows under `correlation_id`.\nAn id that names nothing returns 404. It never falls back to registering a new import: a stale id must fail loudly rather than quietly produce a second one." + } + } + }, + "ValidateErpImportRequest": { + "type": "object", + "required": [ + "integration_id", + "use_case_slug" + ], + "additionalProperties": false, + "properties": { + "integration_id": { + "type": "string" + }, + "use_case_slug": { + "type": "string", + "description": "An inbound use case's slug, unique within its integration." + } + } + }, + "ErpImportUseCaseSuggestion": { + "type": "object", + "required": [ + "integration_id", + "integration_name", + "use_case_slug", + "use_case_name", + "matched_columns", + "entity_types" + ], + "properties": { + "integration_id": { + "type": "string" + }, + "integration_name": { + "type": "string" + }, + "use_case_slug": { + "type": "string" + }, + "use_case_name": { + "type": "string" + }, + "entity_types": { + "type": "integer", + "description": "How many distinct entity schemas this mapping uses." + }, + "matched_columns": { + "type": "integer", + "description": "How many of the FILE's columns this use case reads, by exact trimmed name." + } + } + }, + "SuggestErpImportUseCasesResponse": { + "type": "object", + "required": [ + "file_columns", + "suggestions" + ], + "properties": { + "file_columns": { + "type": "integer", + "description": "Columns in the file. Present even when `suggestions` is empty." + }, + "suggestions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErpImportUseCaseSuggestion" + } + } + } + }, + "ExecuteErpImportRequest": { + "type": "object", + "description": "Confirmation options. Required only when the verdict carries warnings — a clean import needs no body at all.", + "additionalProperties": false, + "properties": { + "ack_warnings": { + "type": "boolean", + "description": "The caller has seen `validation.issues` and accepts them. Required when `validation.warnings` is greater than 0; ignored otherwise." + } + } + }, + "ErpImportIssue": { + "type": "object", + "description": "A problem found during validation, scoped to the file as a whole rather than to individual rows.\n`code` is the translation key and the other fields are its parameters — there is deliberately no message to display. Each code appears at most once, with everything it has to say aggregated into that one entry.", + "required": [ + "code", + "severity" + ], + "properties": { + "code": { + "type": "string", + "description": "Enum of possible issue codes.\n", + "enum": [ + "UNIQUE_ID_COLUMN_MISSING", + "MAPPED_COLUMN_MISSING", + "MALFORMED_ROW", + "INVALID_ENCODING", + "EMPTY_FILE", + "TOO_MANY_ROWS", + "BLANK_ROWS_SKIPPED" + ] + }, + "severity": { + "type": "string", + "enum": [ + "warning", + "blocking" + ] + }, + "columns": { + "type": "array", + "description": "The columns this issue is about, at most one entry per column per entity.\nOn UNIQUE_ID_COLUMN_MISSING the file has NONE of these. Do NOT tell the user that adding one of them is enough: a unique id may read several columns through a JSONata expression, and whether it combines them (`A & B`, both needed) or falls back between them (`A ? A : B`, either will do) is not knowable here.", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The column name, spelled as the mapping reads it." + }, + "entity": { + "type": "string", + "description": "Slug of the entity this column helps identify. UNIQUE_ID_COLUMN_MISSING only." + } + } + } + }, + "row": { + "type": "integer", + "description": "The offending data row, 1-based as the user counts rows. MALFORMED_ROW only." + } + } + }, + "ErpImportValidation": { + "type": "object", + "description": "Validate-phase summary: what the file will create, and whether it may be confirmed. Absent until the validate phase completes. No per-row detail is kept — a rejected file is corrected and imported again.", + "required": [ + "total_rows", + "blocking", + "warnings", + "entities" + ], + "properties": { + "total_rows": { + "type": "integer", + "description": "Data rows the import will act on. Rows with no value in any column are dropped before they are counted, and reported as BLANK_ROWS_SKIPPED — so this can be lower than the line count of the file." + }, + "blocking": { + "type": "integer", + "description": "Blocking problems found, counting per-row ones that are not listed in `issues`." + }, + "warnings": { + "type": "integer" + }, + "entities": { + "type": "object", + "additionalProperties": { + "type": "integer" + }, + "description": "Distinct entities the file expresses, keyed by entity slug." + }, + "issues": { + "type": "array", + "description": "Whole-file issues, at most one per `code`. Do not expect the length to match blocking + warnings: those also count per-row problems, which are recorded for support but never listed here. Warnings here are what `ack_warnings` on `:execute` acknowledges.", + "items": { + "$ref": "#/components/schemas/ErpImportIssue" + } + } + } + }, + "ErpImportProgress": { + "type": "object", + "description": "How far the currently running phase has got. Written at every batch boundary, so it advances during long runs rather than only at the end.\n`total_rows` is ABSENT during the validate phase until the file has been read to the end — there is deliberately no counting pass, since that would be a second unbounded read of the whole file. Render an indeterminate indicator while it is missing: dividing by a missing total yields a determinate bar pinned at 0%, which reads as a hung import.", + "required": [ + "processed_rows" + ], + "properties": { + "processed_rows": { + "type": "integer", + "description": "Rows fully processed. An exact \"the first N rows are done\" watermark, not an estimate — it only advances once a batch has been completely handled." + }, + "total_rows": { + "type": "integer", + "description": "Rows in the file. Known only once a phase has read to EOF; the execute phase has it from the start, because validate recorded it first." + } + } + }, + "ErpImportError": { + "type": "object", + "description": "Why the import failed — present if and only if status = FAILED. `code` is the translation key; for VALIDATION_BLOCKED the specifics are in `validation`.", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Enum of possible error codes.\n", + "enum": [ + "VALIDATION_BLOCKED", + "FILE_FORMAT_UNSUPPORTED", + "FILE_UNAVAILABLE", + "VALIDATE_TIMEOUT", + "IMPORT_TIMEOUT", + "USE_CASE_NOT_USABLE", + "IMPORT_NO_PROGRESS", + "INTERNAL_ERROR" + ] + }, + "message": { + "type": "string", + "description": "One English sentence, derived from `code` so the two always agree. A fallback for a client that has no translation for this code — prefer translating `code`, and never parse this. It deliberately does NOT restate `validation.issues`." + } + } + }, + "ErpImportFilePreview": { + "type": "object", + "description": "Sample of the file's first rows, using the same parser as `:validate`. Registration refuses a file it cannot read, so a created job always includes this.", + "required": [ + "columns", + "rows" + ], + "properties": { + "columns": { + "type": "array", + "description": "Effective column names (trimmed, duplicates collapsed).", + "items": { + "type": "string" + } + }, + "rows": { + "type": "array", + "description": "Up to the first 3 data rows, aligned to `columns`.", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "CreateErpImportResponse": { + "type": "object", + "required": [ + "job" + ], + "properties": { + "job": { + "$ref": "#/components/schemas/ErpImportJob" + }, + "preview": { + "$ref": "#/components/schemas/ErpImportFilePreview" + } + } + }, + "ErpImportJob": { + "type": "object", + "required": [ + "import_id", + "org_id", + "format", + "status", + "s3_input_ref", + "created_at", + "updated_at" + ], + "properties": { + "import_id": { + "type": "string", + "description": "`imp_{ULID}` — time-ordered, also used as the job's correlation_id." + }, + "org_id": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "integration_id": { + "type": "string", + "description": "The integration owning the chosen use case. Absent until the first `:validate`." + }, + "use_case_slug": { + "type": "string", + "description": "The inbound use case whose mapping drives both phases. Absent until the first `:validate`, and MUTABLE — re-validating with a different use case replaces it." + }, + "format": { + "type": "string", + "enum": [ + "csv", + "xlsx" + ] + }, + "status": { + "type": "string", + "description": "PENDING → VALIDATING → READY → PROCESSING → IMPORTED, with FAILED reachable from any working status, and CANCELLING → CANCELLED reachable from VALIDATING or PROCESSING via :abort. Only IMPORTED and CANCELLED are terminal. READY and FAILED both accept a further `:validate`, which is how a wrong use case is corrected without re-uploading the file.\nIMPORTED means every row was handed to the platform, not that the platform finished — per-row outcomes live in monitoring, filtered by correlation_id. A file that fails validation is FAILED with error.code = VALIDATION_BLOCKED.\nREADY is legitimately idle for as long as the user takes to confirm, so it carries no running work and never goes stale.\nCANCELLING is transient and cooperative: the abort has been recorded but the worker only notices at its next batch boundary. Rows already published stay published — a stop is not a rollback.", + "enum": [ + "PENDING", + "VALIDATING", + "READY", + "PROCESSING", + "IMPORTED", + "FAILED", + "CANCELLING", + "CANCELLED" + ] + }, + "s3_input_ref": { + "$ref": "#/components/schemas/S3Reference" + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "description": "Size of the uploaded file, recorded at registration. Present on every job registered from version 1.17.0 onwards; absent on older rows, which were written before it was captured." + }, + "column_count": { + "type": "integer", + "description": "How many effective columns the file's header yielded — the length of the `preview.columns` returned at registration, kept so a job loaded later can still describe its file. The preview ROWS are deliberately not stored: they are a sample for the person about to choose a mapping, not job state. Present on every job registered from version 1.17.0 onwards." + }, + "validation": { + "$ref": "#/components/schemas/ErpImportValidation" + }, + "progress": { + "$ref": "#/components/schemas/ErpImportProgress" + }, + "error": { + "$ref": "#/components/schemas/ErpImportError" + }, + "correlation_id": { + "type": "string", + "description": "Scopes this run in monitoring. Always equal to `import_id`." + }, + "activity_id": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "ErpImportList": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErpImportJob" + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Cursor for the next page, or null when there are no more rows." + } + } + }, + "NotificationHistoryItem": { + "type": "object", + "description": "A single notification-history row (one real notification decision).", + "required": [ + "id", + "type", + "severity", + "title", + "occurred_at", + "notified", + "recipients", + "context", + "created_at" + ], + "properties": { + "id": { + "type": "string", + "description": "Stable history row id (ULID)." + }, + "type": { + "type": "string", + "description": "The notification/rule type (e.g. critical_error, error_threshold, integration_digest)." + }, + "state_transition": { + "type": "string", + "nullable": true, + "description": "The state transition that produced this decision (e.g. OK->ALERTING), when applicable." + }, + "severity": { + "type": "string", + "enum": [ + "error", + "warning", + "info" + ], + "description": "Severity of the decision." + }, + "title": { + "type": "string", + "description": "Human-readable title at decision time." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the decision occurred (newest-first ordering key)." + }, + "notified": { + "type": "boolean", + "description": "Whether the notification was actually sent (true) or suppressed (false)." + }, + "suppressed_reason": { + "type": "string", + "nullable": true, + "enum": [ + "muted", + "debounced", + "recipient_opt_out" + ], + "description": "Why the notification was suppressed (only set when notified is false)." + }, + "recipients": { + "type": "array", + "description": "epilot user ids the notification was (or would have been) delivered to.", + "items": { + "type": "string" + } + }, + "context": { + "type": "object", + "additionalProperties": true, + "description": "Type-specific context captured at decision time." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the history row was written." + } + } + }, + "NotificationHistoryResponse": { + "type": "object", + "required": [ + "history" + ], + "properties": { + "history": { + "type": "array", + "description": "Notification-history rows, newest first.", + "items": { + "$ref": "#/components/schemas/NotificationHistoryItem" + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Opaque base64 cursor for the next page, or null when there are no more rows." + } + } + }, + "TestNotificationRequest": { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "alert", + "digest" + ], + "description": "The kind of notification to render and send." + }, + "type": { + "type": "string", + "description": "The alert type to render when kind=alert (e.g. critical_error, error_threshold)." + }, + "channels": { + "type": "array", + "description": "Delivery channels to use; defaults to the integration's configured channels.", + "items": { + "type": "string", + "enum": [ + "email", + "in_app" + ] + } + } + } + }, + "TestNotificationResponse": { + "type": "object", + "required": [ + "sent", + "recipient", + "channels" + ], + "properties": { + "sent": { + "type": "boolean", + "description": "Whether the test notification was accepted for delivery." + }, + "recipient": { + "type": "string", + "description": "The calling user id the test was sent to (the only recipient)." + }, + "channels": { + "type": "array", + "description": "The channels the test was delivered on.", + "items": { + "type": "string" + } + }, + "notification_id": { + "type": "string", + "nullable": true, + "description": "The svc-notification-api message id, or null when not returned." + } + } + }, + "NotificationStatusResponse": { + "type": "object", + "required": [ + "health", + "rules" + ], + "properties": { + "health": { + "type": "string", + "enum": [ + "healthy", + "alerting", + "muted" + ], + "description": "Rolled-up live status: `muted` when muteUntil is in the future; else `alerting` if any rule is currently ALERTING; else `healthy`.\n" + }, + "evaluated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Most recent per-rule evaluation instant (max lastEvaluatedAt), or null when no rule has been evaluated. Updates on the 5-minute sweep tick.\n" + }, + "rules": { + "type": "array", + "description": "Per-rule status, one entry per configured rule.", + "items": { + "$ref": "#/components/schemas/NotificationRuleStatus" + } + } + } + }, + "NotificationRuleStatus": { + "type": "object", + "required": [ + "rule_id", + "state" + ], + "properties": { + "rule_id": { + "type": "string", + "description": "The rule's stable id (matches the configured rule id)." + }, + "state": { + "type": "string", + "enum": [ + "ok", + "alerting", + "recovered" + ], + "description": "The rule's live AlertState (defaults to `ok` when never evaluated)." + }, + "last_fired_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "ISO instant the rule last entered ALERTING." + }, + "last_cleared_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "ISO instant the rule last cleared back to OK." + }, + "baseline": { + "nullable": true, + "description": "Present only for enabled 'auto'-threshold rules; null otherwise.", + "allOf": [ + { + "$ref": "#/components/schemas/RuleBaselineStatus" + } + ] + } + } + }, + "RuleBaselineStatus": { + "type": "object", + "required": [ + "is_mature" + ], + "properties": { + "is_mature": { + "type": "boolean", + "description": "False during cold start; the sweeper uses the static fallbackThreshold until the baseline's history span is mature.\n" + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "ISO instant the baseline was last computed." + }, + "median": { + "type": "number", + "nullable": true, + "description": "Typical in-scope event volume for the current hour-of-week bucket." + }, + "mad": { + "type": "number", + "nullable": true, + "description": "Median absolute deviation for the current hour-of-week bucket." + }, + "upper": { + "type": "number", + "nullable": true, + "description": "Dynamic alert threshold (median + k·MAD, k by sensitivity) for the current hour-of-week, or null when the bucket is uncovered.\n" + }, + "buckets": { + "type": "array", + "nullable": true, + "description": "Full 168-bucket series; only present when ?include=baseline_series.", + "items": { + "$ref": "#/components/schemas/RuleBaselineBucket" + } + } + } + }, + "RuleBaselineBucket": { + "type": "object", + "required": [ + "dow", + "hour", + "median", + "mad" + ], + "properties": { + "dow": { + "type": "integer", + "minimum": 1, + "maximum": 7, + "description": "Day of week, 1=Monday … 7=Sunday." + }, + "hour": { + "type": "integer", + "minimum": 0, + "maximum": 23, + "description": "Hour of day, 0 … 23 (UTC)." + }, + "median": { + "type": "number" + }, + "mad": { + "type": "number" + } + } + }, + "ErrorResponseBase": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Computer-readable error code" + }, + "message": { + "type": "string", + "description": "Error message" + } + } + }, "ErpEvent": { "type": "object", "required": [ @@ -3476,6 +5383,11 @@ "description": "Optional unique identifier for idempotency - prevents duplicate processing of the same event within 24 hours in context of the same integration. Must contain only alphanumeric characters, hyphens, and underscores.\n", "example": "evt-2025-05-01-12345-create-bp" }, + "correlation_id": { + "type": "string", + "description": "Optional per-event trace id for cross-system tracing (unique per business operation). Overrides the request-level meta.correlation_id for THIS event. When absent, the event inherits the request-level correlation_id; when both are absent, epilot mints its own event_id and the trace is epilot-only. Orthogonal to deduplication_id (idempotency).\n", + "example": "bp-8f3a2c-7d4e-4b1a-9c2f-1e6d5a4b3c21" + }, "use_case_id": { "type": "string", "nullable": true, @@ -3564,7 +5476,7 @@ "additionalProperties": true } ], - "description": "The object data payload - can be either a serialized string or a direct JSON object", + "description": "The object data payload - can be either a serialized string or a direct JSON object.\n\nFor events routed to a mapped (default) use case this is the raw ERP object; the use\ncase's mapping configuration transforms it into entity updates.\n\nFor events routed to a use case whose configuration has `direct: true`, the payload\nMUST be a `DirectPayload` (as an object, or a JSON string): a versioned envelope of\npre-mapped entity and meter reading operations that skip the mapping engine entirely.\nDirect use cases accept JSON only - `format: \"xml\"` events are rejected.\n", "example": "{\"id\":\"BP10001\",\"name\":\"Acme Corporation\",\"type\":\"organization\",\"tax_id\":\"DE123456789\",\"status\":\"active\"}" }, "use_case_slug": { @@ -3582,6 +5494,19 @@ "maxLength": 255, "description": "Optional unique identifier for idempotency - prevents duplicate processing of the same event within 24 hours in context of the same integration. Must contain only alphanumeric characters, hyphens, and underscores.\n", "example": "evt-2025-05-01-12345-bp" + }, + "group_id": { + "type": "string", + "description": "Optional per-event ordering group. Overrides the request-level `group_id` for THIS event; when absent, the event inherits the request-level `group_id` (or the default strict per-integration ordering). Use it when a single batch carries events for unrelated business objects that may be processed in parallel. See the request-level `group_id` for semantics.\n", + "maxLength": 128, + "example": "customer-42" + }, + "correlation_id": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Optional per-event trace id. Overrides the request-level `correlation_id` for THIS event, so a batch carrying several distinct business operations keeps its traces segregated in monitoring instead of merged under one request-level id. When absent, the event inherits the request-level `correlation_id`.\n", + "example": "customer-42-sync-2026-08-26" } } }, @@ -3603,7 +5528,7 @@ }, "group_id": { "type": "string", - "description": "Controls ordering and parallelism for this request's events.\n\nBy default, all events for a given `integration_id` are processed\n**strictly in order, one at a time**. For high-volume integrations this\ncan become a throughput bottleneck.\n\nSet `group_id` to opt into **parallel processing**:\n- Events sharing the same `group_id` are processed in the order received.\n- Events with different `group_id` values are processed in parallel.\n\nTypical usage is to derive `group_id` from a logical partition key in\nyour payload — for example the customer ID, contract ID, or meter ID —\nso updates to the same business object remain ordered while unrelated\nobjects are processed concurrently.\n\nNotes:\n- Up to 20 groups per integration are processed concurrently. Using\n more distinct values than that yields no additional parallelism.\n- Omit this field if strict per-integration ordering is required.\n", + "description": "Controls ordering and parallelism for this request's events.\n\nBy default, all events for a given `integration_id` are processed\n**strictly in order, one at a time**. For high-volume integrations this\ncan become a throughput bottleneck.\n\nSet `group_id` to opt into **parallel processing**:\n- Events sharing the same `group_id` are processed in the order received.\n- Events with different `group_id` values are processed in parallel.\n\nTypical usage is to derive `group_id` from a logical partition key in\nyour payload — for example the customer ID, contract ID, or meter ID —\nso updates to the same business object remain ordered while unrelated\nobjects are processed concurrently.\n\nNotes:\n- Individual events may set their own `group_id`, which overrides this\n request-level value for that event only.\n- Up to 20 groups per integration are processed concurrently. Using\n more distinct values than that yields no additional parallelism.\n- Omit this field if strict per-integration ordering is required.\n", "maxLength": 128, "example": "customer-42" }, @@ -4007,22 +5932,250 @@ "properties": { "autoRefresh": { "$ref": "#/components/schemas/AutoRefreshSettings" + }, + "notifications": { + "$ref": "#/components/schemas/IntegrationNotificationConfig" + } + } + }, + "AutoRefreshSettings": { + "type": "object", + "description": "Auto-refresh settings for keeping integration data fresh", + "properties": { + "enabled": { + "type": "boolean", + "default": false, + "description": "Whether auto-refresh is enabled" + }, + "freshnessThresholdMinutes": { + "type": "integer", + "minimum": 1, + "description": "Maximum age (in minutes) of data before it is considered stale and eligible for refresh" + } + } + }, + "IntegrationNotificationConfig": { + "type": "object", + "description": "Integration monitoring notification configuration. Rides Integration.settings.notifications (camelCase) and surfaces on both v1 and v2 GET/PUT. Unknown keys are stripped server-side to stay forward-compatible with deferred (V2) rule types.", + "required": [ + "enabled", + "recipients", + "defaultChannels", + "rules", + "digest" + ], + "properties": { + "enabled": { + "type": "boolean", + "description": "Master switch for this integration's notifications." + }, + "recipients": { + "type": "array", + "description": "epilot user ids notified for this integration. Same-org membership and per-user notification preferences are enforced at send time (Phases 3–5), not at config-write time.", + "items": { + "$ref": "#/components/schemas/NotificationRecipient" + } + }, + "defaultChannels": { + "$ref": "#/components/schemas/NotificationChannelSet" + }, + "monitoredUseCases": { + "type": "array", + "description": "Integration-level use-case include-filter; absent/empty means all use cases.", + "items": { + "type": "string" + } + }, + "monitoredCodes": { + "type": "array", + "description": "Integration-level code scope; absent/empty resolves to ['_error_']. Accepts concrete monitoring error codes or group sentinels (_error_, _warning_, _success_, _info_, _any_, _parent_).", + "items": { + "type": "string" + } + }, + "rules": { + "type": "array", + "description": "Enabled triggers and their params. A type MAY repeat; capped at 20 rules (enforced at the write boundary).", + "items": { + "$ref": "#/components/schemas/NotificationRule" + } + }, + "digest": { + "$ref": "#/components/schemas/NotificationDigestConfig" + }, + "muteUntil": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "ISO instant; snooze all non-digest alerts until this time. `null` means not muted." + } + } + }, + "NotificationRecipient": { + "type": "object", + "description": "A configured recipient. Only the epilot user_id is stored.", + "required": [ + "user_id" + ], + "properties": { + "user_id": { + "type": "string", + "description": "epilot user id. Same-org membership is enforced at send time (Phases 3–5), which re-validates each recipient against the integration's org before fanning out — it is not enforced at config-write time." + } + } + }, + "NotificationChannelSet": { + "type": "object", + "description": "Delivery channel toggles. New channels added in svc-notification-api inherit here.", + "required": [ + "email", + "in_app" + ], + "properties": { + "email": { + "type": "boolean" + }, + "in_app": { + "type": "boolean" + } + } + }, + "NotificationRule": { + "type": "object", + "description": "A single notification rule. Only the params relevant to a given type are set. The id is optional on write: the server mints a ULID when omitted and preserves a supplied id verbatim (a stable id keeps a rule's AlertState across config saves).", + "required": [ + "type", + "enabled" + ], + "properties": { + "id": { + "type": "string", + "description": "Stable AlertState + baseline key. Optional on write — the server mints a ULID when omitted; a supplied id is preserved verbatim." + }, + "name": { + "type": "string", + "description": "Optional human label disambiguating two rules of the same type." + }, + "type": { + "type": "string", + "description": "Rule trigger type. These are the only supported types; each is produced by a real alerter.", + "enum": [ + "critical_error", + "error_threshold", + "warning_threshold", + "success_rate_drop", + "recovery", + "silence" + ] + }, + "enabled": { + "type": "boolean" + }, + "channels": { + "$ref": "#/components/schemas/NotificationChannelSet" + }, + "codes": { + "type": "array", + "description": "Per-rule code scope. Event-matching rules default to ['_parent_']; silence defaults to ['_any_']. success_rate_drop and recovery take no codes.", + "items": { + "type": "string" + } + }, + "threshold": { + "description": "Count or percentage; 'auto' selects anomaly-baseline mode.", + "oneOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "auto" + ] + } + ] + }, + "sensitivity": { + "type": "string", + "description": "Band width for 'auto' mode.", + "enum": [ + "low", + "medium", + "high" + ] + }, + "fallbackThreshold": { + "type": "number", + "description": "Static value used while the 'auto' baseline is immature (cold start)." + }, + "window": { + "type": "string", + "description": "Evaluation window, e.g. '15m', '1h', '24h'." + }, + "minSampleSize": { + "type": "integer", + "description": "success_rate_drop minimum sample size guard." + }, + "quietPeriod": { + "type": "string", + "description": "silence quiet period, e.g. '12h'." } } }, - "AutoRefreshSettings": { + "NotificationDigestConfig": { "type": "object", - "description": "Auto-refresh settings for keeping integration data fresh", + "description": "Digest schedule and content configuration.", + "required": [ + "enabled", + "frequency", + "timeOfDay", + "timezone", + "channels", + "includeHealthy", + "skipIfEmpty" + ], "properties": { "enabled": { - "type": "boolean", - "default": false, - "description": "Whether auto-refresh is enabled" + "type": "boolean" }, - "freshnessThresholdMinutes": { + "frequency": { + "type": "string", + "enum": [ + "daily", + "weekly" + ] + }, + "dayOfWeek": { "type": "integer", - "minimum": 1, - "description": "Maximum age (in minutes) of data before it is considered stale and eligible for refresh" + "description": "Weekly only. 0 = Sunday … 6 = Saturday.", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + "timeOfDay": { + "type": "string", + "description": "HH:mm" + }, + "timezone": { + "type": "string", + "description": "IANA timezone, e.g. 'Europe/Berlin'." + }, + "channels": { + "$ref": "#/components/schemas/NotificationChannelSet" + }, + "includeHealthy": { + "type": "boolean", + "description": "List all integrations vs. only ones with issues." + }, + "skipIfEmpty": { + "type": "boolean", + "description": "Suppress the digest when nothing happened." } } }, @@ -4132,6 +6285,10 @@ "type": "object", "description": "Configuration for inbound use cases (ERP to epilot)", "properties": { + "direct": { + "type": "boolean", + "description": "Enables direct mode for this use case. When `true`, every event routed to this use\ncase must carry a `DirectPayload` - pre-mapped entity and meter reading operations -\ninstead of raw ERP data, and the mapping engine is skipped entirely. Everything else\nin the inbound pipeline (deduplication, ordering, create-vs-update resolution,\nrelation resolution, monitoring) behaves exactly as in mapped mode.\n\nIn direct mode, `entities` doubles as an optional allowlist: when non-empty, each\noperation's `entity_slug` must match an entry's `entity_schema`, and when that entry\ndeclares `unique_ids`, the operation's unique-id keys must be exactly that set (or\njust `_id`). Entries need only `entity_schema` and `unique_ids` - `fields` is not\nrequired and is ignored. `meter_readings` is ignored in direct mode.\n\nFlag changes take up to 5 minutes to propagate (configuration cache).\n" + }, "entities": { "type": "array", "description": "Array of entity configurations for this event", @@ -4161,6 +6318,11 @@ "description": "The Event Catalog event name that triggers this outbound flow", "example": "contract.created" }, + "event_filter": { + "type": "string", + "description": "JSONata boolean predicate over the hydrated event payload. The use case handles the\nevent only when this evaluates truthy; when absent it handles every event of its name,\nwhich is the behaviour of every pre-existing configuration.\n\nThis is where event scoping belongs — narrowing to certain ticket purposes, contract\ntypes or channels. Evaluation input is the full hydrated event, so relation nodes such\nas `ticket` and `contact` are populated. An expression that throws is treated as no\nmatch and logged, so one malformed filter cannot block the other use cases subscribed\nto the same event.\n", + "example": "$count(ticket._purpose[$ in ['bae4b4d1-d728-49ef-92ff-54486dd301b4']]) > 0" + }, "mappings": { "type": "array", "minItems": 1, @@ -4168,6 +6330,15 @@ "$ref": "#/components/schemas/OutboundMapping" }, "description": "List of mappings that transform and deliver the event" + }, + "ack_tracking": { + "type": "string", + "enum": [ + "on", + "off" + ], + "default": "on", + "description": "Whether this use case participates in the acknowledgement protocol.\n\n`on` (the default, and the behaviour of every pre-existing use case) records an\nACK_PENDING on each event and expects the consumer to confirm receipt via\n`POST /v1/erp/tracking/acknowledgement`; unconfirmed events raise ACK_TIMEOUT after\nthe timeout window.\n\n`off` opts the use case out entirely: no tracking row, no ACK_PENDING, no\nACK_TIMEOUT. Set it for consumers that never acknowledge — otherwise every event\nproduces a guaranteed timeout warning — and for deliveries that already keep their\nown durable per-item record, such as `file_proxy`.\n\nThe tracking row is per-event, not per-use-case, so it is suppressed only when EVERY\nenabled use case matching the event has opted out.\n" } } }, @@ -4175,8 +6346,7 @@ "type": "object", "required": [ "entity_schema", - "unique_ids", - "fields" + "unique_ids" ], "properties": { "entity_schema": { @@ -4222,7 +6392,7 @@ }, "fields": { "type": "array", - "description": "Field mapping definitions", + "description": "Field mapping definitions. Required for mapped-mode entity configurations; may be\nomitted for allowlist entries of a `direct: true` use case, where no mapping runs.\n", "items": { "$ref": "#/components/schemas/IntegrationEntityField" } @@ -4888,7 +7058,11 @@ "change_description": { "type": "string", "maxLength": 2000, - "description": "Description of the last change made to this use case" + "description": "Reason given for the update that produced the current version. Absent when the update supplied none — it is not inherited from the previous version.\n" + }, + "changed_by": { + "type": "string", + "description": "User ID recorded for the update that produced the current version. Absent when the update had no user context, e.g. an automated sync.\n" }, "created_at": { "type": "string", @@ -5367,13 +7541,233 @@ } ] }, - "UpdateManagedCallUseCaseRequest": { + "UpdateManagedCallUseCaseRequest": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUseCaseRequestBase" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "managed_call" + ], + "description": "Use case type for managed API calls" + }, + "configuration": { + "$ref": "#/components/schemas/ManagedCallOperationConfig" + }, + "type_annotations": { + "$ref": "#/components/schemas/TypeAnnotations" + } + } + } + ] + }, + "UpdateSecureProxyUseCaseRequest": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUseCaseRequestBase" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "secure_proxy" + ], + "description": "Use case type" + }, + "configuration": { + "$ref": "#/components/schemas/SecureProxyUseCaseConfiguration" + } + } + } + ] + }, + "UseCaseHistoryEntry": { + "oneOf": [ + { + "$ref": "#/components/schemas/InboundUseCaseHistoryEntry" + }, + { + "$ref": "#/components/schemas/OutboundUseCaseHistoryEntry" + }, + { + "$ref": "#/components/schemas/FileProxyUseCaseHistoryEntry" + }, + { + "$ref": "#/components/schemas/ManagedCallUseCaseHistoryEntry" + }, + { + "$ref": "#/components/schemas/SecureProxyUseCaseHistoryEntry" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "inbound": "#/components/schemas/InboundUseCaseHistoryEntry", + "outbound": "#/components/schemas/OutboundUseCaseHistoryEntry", + "file_proxy": "#/components/schemas/FileProxyUseCaseHistoryEntry", + "managed_call": "#/components/schemas/ManagedCallUseCaseHistoryEntry", + "secure_proxy": "#/components/schemas/SecureProxyUseCaseHistoryEntry" + } + } + }, + "UseCaseHistoryEntryBase": { + "type": "object", + "required": [ + "id", + "useCaseId", + "integrationId", + "name", + "type", + "enabled", + "created_at", + "updated_at", + "history_created_at" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this history entry" + }, + "useCaseId": { + "type": "string", + "format": "uuid", + "description": "Reference to the parent use case" + }, + "integrationId": { + "type": "string", + "format": "uuid", + "description": "Parent integration ID" + }, + "name": { + "type": "string", + "description": "Use case name at this point in history" + }, + "slug": { + "type": "string", + "description": "Use case slug at this point in history" + }, + "enabled": { + "type": "boolean", + "description": "Whether the use case was enabled at this point in history" + }, + "change_description": { + "type": "string", + "description": "Description of the change that was made at this point in history" + }, + "changed_by": { + "type": "string", + "description": "User ID of the user who made the change that produced this history entry" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO-8601 timestamp when the use case was originally created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "ISO-8601 timestamp of this historical snapshot (before the update)" + }, + "history_created_at": { + "type": "string", + "format": "date-time", + "description": "ISO-8601 timestamp when this history entry was created" + } + } + }, + "InboundUseCaseHistoryEntry": { + "allOf": [ + { + "$ref": "#/components/schemas/UseCaseHistoryEntryBase" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "inbound" + ], + "description": "Use case type" + }, + "configuration": { + "$ref": "#/components/schemas/InboundIntegrationEventConfiguration" + } + } + } + ] + }, + "OutboundUseCaseHistoryEntry": { + "allOf": [ + { + "$ref": "#/components/schemas/UseCaseHistoryEntryBase" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "outbound" + ], + "description": "Use case type" + }, + "configuration": { + "$ref": "#/components/schemas/OutboundIntegrationEventConfiguration" + } + } + } + ] + }, + "FileProxyUseCaseHistoryEntry": { + "allOf": [ + { + "$ref": "#/components/schemas/UseCaseHistoryEntryBase" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "file_proxy" + ], + "description": "Use case type" + }, + "configuration": { + "$ref": "#/components/schemas/FileProxyUseCaseConfiguration" + } + } + } + ] + }, + "ManagedCallUseCaseHistoryEntry": { "allOf": [ { - "$ref": "#/components/schemas/UpdateUseCaseRequestBase" + "$ref": "#/components/schemas/UseCaseHistoryEntryBase" }, { "type": "object", + "required": [ + "type" + ], "properties": { "type": { "type": "string", @@ -5387,18 +7781,25 @@ }, "type_annotations": { "$ref": "#/components/schemas/TypeAnnotations" + }, + "types_locked": { + "type": "boolean", + "description": "Whether types have been generated for this use case" } } } ] }, - "UpdateSecureProxyUseCaseRequest": { + "SecureProxyUseCaseHistoryEntry": { "allOf": [ { - "$ref": "#/components/schemas/UpdateUseCaseRequestBase" + "$ref": "#/components/schemas/UseCaseHistoryEntryBase" }, { "type": "object", + "required": [ + "type" + ], "properties": { "type": { "type": "string", @@ -5414,1192 +7815,1672 @@ } ] }, - "UseCaseHistoryEntry": { - "oneOf": [ - { - "$ref": "#/components/schemas/InboundUseCaseHistoryEntry" + "SecureProxyUseCaseConfiguration": { + "type": "object", + "required": [ + "vpc_mode" + ], + "description": "Configuration for secure_proxy use cases. Defines how to route requests through a secure VPC.\n", + "properties": { + "vpc_mode": { + "type": "string", + "enum": [ + "static_ip", + "secure_link" + ], + "readOnly": true, + "description": "VPC routing mode. Read-only after creation.\n- static_ip: Routes through a VPC with static outbound IP (NAT Gateway) for IP-allowlisted external APIs.\n- secure_link: Routes through a VPN VPC for accessing private customer networks.\n" }, - { - "$ref": "#/components/schemas/OutboundUseCaseHistoryEntry" + "allowed_domains": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true, + "description": "Domain whitelist for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script.\nSupports exact match (e.g., \"api.wemag.com\") and wildcard prefix (e.g., \"*.wemag.com\").\n" }, - { - "$ref": "#/components/schemas/FileProxyUseCaseHistoryEntry" + "allowed_ips": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true, + "description": "IP allowlist (CIDR notation) for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script.\nRequired for secure_link mode. All DNS-resolved IPs must match at least one range.\nExample: [\"10.0.1.0/24\", \"192.168.1.0/24\"]\n" + } + } + }, + "SecureProxyWhitelist": { + "type": "object", + "required": [ + "vpc_mode", + "allowed_domains", + "allowed_ips" + ], + "description": "Current whitelist state for a secure_proxy use case. vpc_mode is read-only\ncontext so the UI can show the user what mode the pool is in.\n", + "properties": { + "vpc_mode": { + "type": "string", + "nullable": true, + "enum": [ + "static_ip", + "secure_link" + ], + "description": "VPC routing mode (read-only). `null` if not yet set." }, - { - "$ref": "#/components/schemas/ManagedCallUseCaseHistoryEntry" + "allowed_domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Exact domain (\"api.example.com\") or wildcard prefix (\"*.example.com\").\nWildcards must have at least 2 labels in the suffix.\n" }, - { - "$ref": "#/components/schemas/SecureProxyUseCaseHistoryEntry" + "allowed_ips": { + "type": "array", + "items": { + "type": "string" + }, + "description": "CIDR-notation IP ranges, e.g. \"10.0.0.0/24\"." } - ], - "discriminator": { - "propertyName": "type", - "mapping": { - "inbound": "#/components/schemas/InboundUseCaseHistoryEntry", - "outbound": "#/components/schemas/OutboundUseCaseHistoryEntry", - "file_proxy": "#/components/schemas/FileProxyUseCaseHistoryEntry", - "managed_call": "#/components/schemas/ManagedCallUseCaseHistoryEntry", - "secure_proxy": "#/components/schemas/SecureProxyUseCaseHistoryEntry" + } + }, + "SecureProxyWhitelistUpdate": { + "type": "object", + "description": "Partial update for a secure_proxy whitelist. At least one of\n`allowed_domains` or `allowed_ips` must be provided.\n\nPer-field semantics:\n - **omitted** — the field is not modified; the stored value is preserved.\n - **non-empty array** — the stored value is replaced with the supplied list.\n - **empty array (`[]`)** — the list is cleared (stored as `[]`). This is the\n canonical way to remove all entries from a list. `null` is not accepted.\n", + "minProperties": 1, + "properties": { + "allowed_domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Exact domain (\"api.example.com\") or wildcard prefix (\"*.example.com\").\nWildcards must have at least 2 labels in the suffix.\nPass `[]` to clear all allowed domains.\n" + }, + "allowed_ips": { + "type": "array", + "items": { + "type": "string" + }, + "description": "CIDR-notation IP ranges, e.g. \"10.0.0.0/24\".\nPass `[]` to clear all allowed IPs.\n" } } }, - "UseCaseHistoryEntryBase": { + "SecureProxySummary": { "type": "object", "required": [ "id", - "useCaseId", - "integrationId", "name", - "type", "enabled", - "created_at", - "updated_at", - "history_created_at" + "vpc_mode", + "integration_id", + "integration_name" ], "properties": { "id": { "type": "string", - "format": "uuid", - "description": "Unique identifier for this history entry" + "format": "uuid" }, - "useCaseId": { + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "vpc_mode": { + "type": "string", + "enum": [ + "static_ip", + "secure_link" + ] + }, + "allowed_domains": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowed_ips": { + "type": "array", + "items": { + "type": "string" + } + }, + "integration_id": { + "type": "string", + "format": "uuid" + }, + "integration_name": { + "type": "string" + } + } + }, + "SecureProxyRequest": { + "type": "object", + "required": [ + "url", + "method", + "integration_id" + ], + "properties": { + "integration_id": { "type": "string", "format": "uuid", - "description": "Reference to the parent use case" + "description": "Integration ID that owns the secure_proxy use case" }, - "integrationId": { + "use_case_id": { "type": "string", "format": "uuid", - "description": "Parent integration ID" + "description": "Use case ID (provide either use_case_id or use_case_slug)" }, - "name": { + "use_case_slug": { "type": "string", - "description": "Use case name at this point in history" + "description": "Use case slug (provide either use_case_id or use_case_slug)" }, - "slug": { + "url": { "type": "string", - "description": "Use case slug at this point in history" + "format": "uri", + "description": "Target URL to proxy the request to" }, - "enabled": { - "type": "boolean", - "description": "Whether the use case was enabled at this point in history" + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "description": "HTTP method" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Request headers to forward" + }, + "body": { + "description": "Request body (string or JSON)" + }, + "response_type": { + "type": "string", + "enum": [ + "json", + "binary" + ], + "default": "json", + "description": "Expected response type" + } + } + }, + "SecureProxyResponse": { + "type": "object", + "properties": { + "status_code": { + "type": "integer", + "description": "HTTP status code from the upstream response" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Response headers from upstream" + }, + "body": { + "description": "Response body from upstream. When `status_code` is 502 and the target never produced an HTTP response (TLS/connection/DNS failure or timeout), this is a `SecureProxyUpstreamError` describing the underlying cause." + } + } + }, + "SecureProxyUpstreamError": { + "type": "object", + "description": "Error payload returned when epilot could not obtain an HTTP response from the proxied target. The failure is epilot-generated (HTTP 502) but the cause is usually remote-side; `code`/`reason` make that attributable.", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Short error category.", + "enum": [ + "Upstream network error", + "Upstream error" + ] }, - "change_description": { + "code": { "type": "string", - "description": "Description of the change that was made at this point in history" + "description": "Underlying Node.js/axios error code when available (e.g. `UNABLE_TO_VERIFY_LEAF_SIGNATURE`, `ECONNREFUSED`, `ETIMEDOUT`, `ENOTFOUND`)." }, - "changed_by": { + "reason": { "type": "string", - "description": "User ID of the user who made the change that produced this history entry" + "description": "Human-readable explanation, present only for well-known codes (TLS/certificate, DNS and connection failures)." + } + } + }, + "ManagedCallOperationConfig": { + "type": "object", + "required": [ + "operation" + ], + "description": "Configuration for managed_call use cases. Defines a single API operation with JSONata mapping.", + "properties": { + "operation": { + "$ref": "#/components/schemas/ManagedCallOperation" }, - "created_at": { + "request_mapping": { "type": "string", - "format": "date-time", - "description": "ISO-8601 timestamp when the use case was originally created" + "description": "JSONata expression for outbound body transformation" }, - "updated_at": { + "response_mapping": { "type": "string", - "format": "date-time", - "description": "ISO-8601 timestamp of this historical snapshot (before the update)" + "description": "JSONata expression for inbound response transformation" }, - "history_created_at": { + "inbound_use_case_slug": { "type": "string", - "format": "date-time", - "description": "ISO-8601 timestamp when this history entry was created" + "description": "Slug of the inbound use case to route responses to for async entity processing.\nWhen set, the managed call response is queued to the inbound pipeline and processed\nusing the referenced inbound use case's mapping configuration.\n" } } }, - "InboundUseCaseHistoryEntry": { - "allOf": [ - { - "$ref": "#/components/schemas/UseCaseHistoryEntryBase" + "ManagedCallOperation": { + "type": "object", + "required": [ + "method", + "path" + ], + "description": "HTTP operation configuration for managed calls", + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ] }, - { + "path": { + "type": "string", + "description": "URL path template with {{variable}} interpolation" + }, + "headers": { "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "inbound" - ], - "description": "Use case type" - }, - "configuration": { - "$ref": "#/components/schemas/InboundIntegrationEventConfiguration" - } + "description": "Custom HTTP headers for the request. Values support {{variable}} interpolation from the request payload and {{env.VAR}} references for environment variables.", + "additionalProperties": { + "type": "string" } - } - ] - }, - "OutboundUseCaseHistoryEntry": { - "allOf": [ - { - "$ref": "#/components/schemas/UseCaseHistoryEntryBase" }, - { + "query_params": { "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "outbound" - ], - "description": "Use case type" - }, - "configuration": { - "$ref": "#/components/schemas/OutboundIntegrationEventConfiguration" - } + "description": "Query parameters for the request. Values support {{variable}} interpolation from the request payload.", + "additionalProperties": { + "type": "string" } } - ] + } }, - "FileProxyUseCaseHistoryEntry": { - "allOf": [ - { - "$ref": "#/components/schemas/UseCaseHistoryEntryBase" + "ManagedCallExecuteRequest": { + "type": "object", + "required": [ + "integration_id" + ], + "properties": { + "integration_id": { + "type": "string", + "format": "uuid", + "description": "Integration ID" }, - { + "payload": { + "type": "object", + "additionalProperties": true, + "description": "Request payload for the managed call operation" + }, + "correlation_id": { + "type": "string", + "description": "Correlation ID for tracing related events (auto-generated if not provided)" + } + } + }, + "ManagedCallExecuteResponse": { + "description": "The response from a managed call execution.\nOn success, returns the JSONata-mapped response data directly (no wrapper).\nThe shape is entirely defined by your response_mapping JSONata expression.\nIf no response_mapping is configured, returns the raw external API response.\nCheck the X-Inbound-Event-Id header for inbound pipeline tracking when inbound routing is configured.\n", + "additionalProperties": true + }, + "ManagedCallErrorResponse": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { "type": "object", "required": [ - "type" + "code", + "message" ], "properties": { - "type": { + "code": { "type": "string", - "enum": [ - "file_proxy" - ], - "description": "Use case type" + "description": "Error code (e.g., EXTERNAL_API_ERROR, SSRF_BLOCKED, MANAGED_CALL_EXECUTION_ERROR)" }, - "configuration": { - "$ref": "#/components/schemas/FileProxyUseCaseConfiguration" + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "details": { + "type": "object", + "additionalProperties": true, + "description": "Additional error details (e.g., status code and body for external API errors)" } } } - ] + } }, - "ManagedCallUseCaseHistoryEntry": { - "allOf": [ - { - "$ref": "#/components/schemas/UseCaseHistoryEntryBase" + "TypeAnnotations": { + "type": "object", + "description": "Developer-provided type annotations for a use case's request and response fields", + "properties": { + "request": { + "type": "object", + "description": "Type annotations for request fields, keyed by dot-path (e.g., \"vendors[].id\" -> \"string\")", + "additionalProperties": { + "type": "string" + } }, - { + "response": { "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "managed_call" - ], - "description": "Use case type for managed API calls" - }, - "configuration": { - "$ref": "#/components/schemas/ManagedCallOperationConfig" - }, - "type_annotations": { - "$ref": "#/components/schemas/TypeAnnotations" - }, - "types_locked": { - "type": "boolean", - "description": "Whether types have been generated for this use case" - } + "description": "Type annotations for response fields", + "additionalProperties": { + "type": "string" } } - ] + } }, - "SecureProxyUseCaseHistoryEntry": { - "allOf": [ - { - "$ref": "#/components/schemas/UseCaseHistoryEntryBase" + "TypeDescriptor": { + "type": "object", + "description": "Describes the inferred type shape of a JSONata expression", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "object", + "array", + "string", + "number", + "boolean", + "null", + "unknown", + "union" + ] }, - { + "properties": { "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "secure_proxy" - ], - "description": "Use case type" - }, - "configuration": { - "$ref": "#/components/schemas/SecureProxyUseCaseConfiguration" + "description": "For kind=object, the properties and their type descriptors", + "additionalProperties": { + "$ref": "#/components/schemas/TypeDescriptor" + } + }, + "items": { + "description": "For kind=array, the type of array items", + "allOf": [ + { + "$ref": "#/components/schemas/TypeDescriptor" } + ] + }, + "source": { + "type": "string", + "description": "For kind=unknown, the JSONata path that produced this value" + }, + "variants": { + "type": "array", + "description": "For kind=union, the variant types", + "items": { + "$ref": "#/components/schemas/TypeDescriptor" } } - ] + } }, - "SecureProxyUseCaseConfiguration": { + "UseCaseTypePreview": { "type": "object", + "description": "Scaffolded type descriptors for a single use case", "required": [ - "vpc_mode" + "slug", + "request_shape", + "response_shape" ], - "description": "Configuration for secure_proxy use cases. Defines how to route requests through a secure VPC.\n", "properties": { - "vpc_mode": { - "type": "string", - "enum": [ - "static_ip", - "secure_link" - ], - "readOnly": true, - "description": "VPC routing mode. Read-only after creation.\n- static_ip: Routes through a VPC with static outbound IP (NAT Gateway) for IP-allowlisted external APIs.\n- secure_link: Routes through a VPN VPC for accessing private customer networks.\n" + "slug": { + "type": "string" }, - "allowed_domains": { + "name": { + "type": "string" + }, + "request_shape": { + "$ref": "#/components/schemas/TypeDescriptor" + }, + "response_shape": { + "$ref": "#/components/schemas/TypeDescriptor" + }, + "existing_annotations": { + "$ref": "#/components/schemas/TypeAnnotations" + } + } + }, + "GenerateTypesPreviewResponse": { + "type": "object", + "required": [ + "use_cases" + ], + "properties": { + "integration_name": { + "type": "string" + }, + "use_cases": { "type": "array", "items": { - "type": "string" - }, - "readOnly": true, - "description": "Domain whitelist for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script.\nSupports exact match (e.g., \"api.wemag.com\") and wildcard prefix (e.g., \"*.wemag.com\").\n" + "$ref": "#/components/schemas/UseCaseTypePreview" + } }, - "allowed_ips": { + "previous_version": { + "type": "string", + "description": "Previously generated version, if any" + }, + "suggested_version": { + "type": "string", + "description": "Suggested next version based on detected changes" + }, + "suggested_bump": { + "type": "string", + "enum": [ + "major", + "minor" + ] + }, + "detected_changes": { "type": "array", "items": { - "type": "string" - }, - "readOnly": true, - "description": "IP allowlist (CIDR notation) for secure_link mode. Admin-only — can only be modified directly in DynamoDB via admin script.\nRequired for secure_link mode. All DNS-resolved IPs must match at least one range.\nExample: [\"10.0.1.0/24\", \"192.168.1.0/24\"]\n" + "type": "object", + "required": [ + "slug", + "field", + "type", + "description" + ], + "properties": { + "slug": { + "type": "string" + }, + "field": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "breaking", + "non-breaking" + ] + }, + "description": { + "type": "string" + } + } + } } } }, - "SecureProxyWhitelist": { + "GenerateTypesRequest": { "type": "object", "required": [ - "vpc_mode", - "allowed_domains", - "allowed_ips" + "package_name", + "version" ], - "description": "Current whitelist state for a secure_proxy use case. vpc_mode is read-only\ncontext so the UI can show the user what mode the pool is in.\n", "properties": { - "vpc_mode": { + "package_name": { "type": "string", - "nullable": true, - "enum": [ - "static_ip", - "secure_link" - ], - "description": "VPC routing mode (read-only). `null` if not yet set." + "description": "npm package name", + "example": "@epilot/hems-cleverpv" }, - "allowed_domains": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Exact domain (\"api.example.com\") or wildcard prefix (\"*.example.com\").\nWildcards must have at least 2 labels in the suffix.\n" + "version": { + "type": "string", + "description": "Package version", + "example": "1.0.0" }, - "allowed_ips": { - "type": "array", - "items": { - "type": "string" - }, - "description": "CIDR-notation IP ranges, e.g. \"10.0.0.0/24\"." + "description": { + "type": "string" + }, + "domain_package": { + "type": "string", + "description": "Domain standard package to extend", + "example": "@epilot/hems" + }, + "domain_map_name": { + "type": "string", + "description": "Name of the domain UseCaseMap to extend", + "example": "HemsUseCaseMap" + }, + "annotations": { + "type": "object", + "description": "Type annotations per use case slug", + "additionalProperties": { + "$ref": "#/components/schemas/TypeAnnotations" + } } } }, - "SecureProxyWhitelistUpdate": { + "GenerateTypesResponse": { "type": "object", - "description": "Partial update for a secure_proxy whitelist. At least one of\n`allowed_domains` or `allowed_ips` must be provided.\n\nPer-field semantics:\n - **omitted** — the field is not modified; the stored value is preserved.\n - **non-empty array** — the stored value is replaced with the supplied list.\n - **empty array (`[]`)** — the list is cleared (stored as `[]`). This is the\n canonical way to remove all entries from a list. `null` is not accepted.\n", - "minProperties": 1, + "required": [ + "package_name", + "version", + "files" + ], "properties": { - "allowed_domains": { - "type": "array", - "items": { + "package_name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "type": "object", + "description": "Generated file contents keyed by file path", + "additionalProperties": { "type": "string" - }, - "description": "Exact domain (\"api.example.com\") or wildcard prefix (\"*.example.com\").\nWildcards must have at least 2 labels in the suffix.\nPass `[]` to clear all allowed domains.\n" + } }, - "allowed_ips": { + "warnings": { "type": "array", "items": { "type": "string" - }, - "description": "CIDR-notation IP ranges, e.g. \"10.0.0.0/24\".\nPass `[]` to clear all allowed IPs.\n" + } } } }, - "SecureProxySummary": { + "CommitTypesRequest": { "type": "object", "required": [ - "id", - "name", - "enabled", - "vpc_mode", - "integration_id", - "integration_name" + "package_name", + "version" ], "properties": { - "id": { + "package_name": { "type": "string", - "format": "uuid" - }, - "name": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "enabled": { - "type": "boolean" + "description": "npm package name", + "example": "@epilot/hems-cleverpv" }, - "vpc_mode": { + "version": { "type": "string", - "enum": [ - "static_ip", - "secure_link" - ] + "description": "Package version", + "example": "1.0.0" }, - "allowed_domains": { - "type": "array", - "items": { - "type": "string" + "annotations": { + "type": "object", + "description": "Type annotations per use case slug", + "additionalProperties": { + "$ref": "#/components/schemas/TypeAnnotations" } + } + } + }, + "CommitTypesResponse": { + "type": "object", + "required": [ + "committed" + ], + "properties": { + "committed": { + "type": "boolean" }, - "allowed_ips": { + "warnings": { "type": "array", "items": { "type": "string" } - }, - "integration_id": { - "type": "string", - "format": "uuid" - }, - "integration_name": { - "type": "string" } } }, - "SecureProxyRequest": { + "FileProxyUseCaseConfiguration": { "type": "object", "required": [ - "url", - "method", - "integration_id" + "steps" ], + "description": "Configuration for file_proxy use cases. Defines how to authenticate and move files\nbetween epilot and an external document system, in either direction (see `direction`).\n\n**Download** (`direction: download`, the default) fetches a file from the external system\nand serves it to a browser. The download URL always requires `orgId`, `integrationId`, and\neither `useCaseSlug` (recommended) or `useCaseId` (legacy UUID) as query parameters.\nThe `orgId` is included in the signed URL to establish organization context without\nrequiring authentication. Additional use-case-specific parameters are declared in the\n`params` array. `response` is REQUIRED for download use cases.\n\n**Upload** (`direction: upload`) pushes epilot files to the external system. It is not\nreachable over the download endpoint; an outbound use case points at it via a `file_proxy`\ndelivery, and this configuration owns everything about what gets sent: `fan_out` decides\nhow many deliveries one event produces, and every step builds its own request body with\n`body_jsonata` — or leaves it empty to send the delivery's files unchanged.\n`upload` is REQUIRED and `response` MUST be omitted.\n\nTwo expression languages, split by what they produce. **Handlebars composes strings**:\n`url` and `headers` on upload, plus a form-encoded, XML or plain-text `body` on\ndownload. **JSONata produces data**: `body_jsonata` and the per-step `enabled`.\nJSONata omits keys whose value is undefined, so an optional field needs no conditional\nguard — it is simply absent from the serialized body.\n\nHandlebars templates are rendered EXACTLY ONCE, against a single context holding `env`,\n`file_data`, `steps` and `auth_token`. Writing\n`{{ env.some_var }}` resolves it. The legacy `\\{{ env.some_var }}` escape belongs to the\ntwo-pass renderer and is NOT rewritten here — it renders as the literal text\n`{{ env.some_var }}`, which is rejected (see below) rather than shipped.\n\nTwo guards run on every rendered upload template, because single-pass rendering fails\nquietly by default. Both are terminal, and each names what to fix:\n\n- **residual `{{` after rendering** — a configuration still carrying the `\\{{` escape.\n Rewrite it without the backslash.\n- **a referenced `env` key absent from the environment** — checked BEFORE the URL is\n parsed, because an empty value in host position turns\n `https://{{env.host}}/document/import` into `https:///document/import`, whose host\n then parses as `document`. Provision the environment variable. A key that exists and\n is legitimately empty is fine; only absence fails.\n\nOpenAPI 3.0 cannot express this conditional requiredness, so it is enforced by the\nserver-side validator, which returns an explicit message naming the offending field.\n", "properties": { - "integration_id": { + "direction": { "type": "string", - "format": "uuid", - "description": "Integration ID that owns the secure_proxy use case" + "enum": [ + "download", + "upload" + ], + "default": "download", + "description": "Direction of file travel. `download` (default) pulls a file from the external system\ninto epilot; `upload` pushes an epilot file out to the external system. Omitted means\n`download`, so every pre-existing configuration keeps its exact meaning.\n\nNote this is the direction of the FILE, not the epilot use-case type — an `upload`\nfile_proxy use case is still a `file_proxy` use case, never an `outbound` one.\n" }, - "use_case_id": { - "type": "string", - "format": "uuid", - "description": "Use case ID (provide either use_case_id or use_case_slug)" + "upload": { + "$ref": "#/components/schemas/FileProxyUploadConfig" }, - "use_case_slug": { - "type": "string", - "description": "Use case slug (provide either use_case_id or use_case_slug)" + "fan_out": { + "$ref": "#/components/schemas/FileProxyFanOutConfig" }, - "url": { - "type": "string", - "format": "uri", - "description": "Target URL to proxy the request to" + "secure_proxy": { + "description": "Optional secure proxy attachment for routing all outbound file proxy requests.\nOnly `use_case_slug` is supported and the referenced secure_proxy use case\nmust belong to the same integration.\n", + "allOf": [ + { + "$ref": "#/components/schemas/FileProxySecureProxyAttachment" + } + ] }, - "method": { - "type": "string", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE" - ], - "description": "HTTP method" + "auth": { + "$ref": "#/components/schemas/FileProxyAuth" }, - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" + "params": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileProxyParam" }, - "description": "Request headers to forward" - }, - "body": { - "description": "Request body (string or JSON)" + "description": "Download-only. Additional use-case-specific parameters expected in the download URL\nquery string (beyond the required orgId, integrationId, and useCaseSlug or useCaseId).\nRejected when `direction` is `upload`.\n" }, - "response_type": { - "type": "string", - "enum": [ - "json", - "binary" - ], - "default": "json", - "description": "Expected response type" - } - } - }, - "SecureProxyResponse": { - "type": "object", - "properties": { - "status_code": { - "type": "integer", - "description": "HTTP status code from the upstream response" + "allowed_origins": { + "type": "array", + "items": { + "type": "string", + "format": "uri", + "pattern": "^https?://" + }, + "description": "Download-only. Additional origins permitted to call /download for this use case\n(CORS, exact match). Portal origins are always allowed. Rejected when `direction`\nis `upload`.\n" }, - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" + "steps": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/FileProxyStep" }, - "description": "Response headers from upstream" + "description": "Ordered list of HTTP steps to execute. For `download` these retrieve the file; for\n`upload` they deliver it, each assembling its own request body — `body_jsonata` for\nJSON, `body` for anything else.\n" }, - "body": { - "description": "Response body from upstream" + "response": { + "$ref": "#/components/schemas/FileProxyResponseConfig" + }, + "prevent_indirect_serving": { + "type": "boolean", + "default": false, + "description": "Download-only; rejected when `direction` is `upload`.\nWhen `true`, this use case is served via the streaming endpoint: mapped file URLs\nare built as `/stream/download`, files of any size are streamed inline over HTTP\nresponse streaming, and buffered `/download` requests for oversize files are\n307-redirected to `/stream`. Files never transit epilot's temporary S3 storage on\nthe streaming path. Defaults to `false` (small files are served directly and large\nfiles are transparently served via a temporary S3 redirect).\n" } } }, - "ManagedCallOperationConfig": { + "FileProxySecureProxyAttachment": { "type": "object", "required": [ - "operation" + "use_case_slug" ], - "description": "Configuration for managed_call use cases. Defines a single API operation with JSONata mapping.", + "additionalProperties": false, "properties": { - "operation": { - "$ref": "#/components/schemas/ManagedCallOperation" - }, - "request_mapping": { - "type": "string", - "description": "JSONata expression for outbound body transformation" - }, - "response_mapping": { - "type": "string", - "description": "JSONata expression for inbound response transformation" - }, - "inbound_use_case_slug": { + "use_case_slug": { "type": "string", - "description": "Slug of the inbound use case to route responses to for async entity processing.\nWhen set, the managed call response is queued to the inbound pipeline and processed\nusing the referenced inbound use case's mapping configuration.\n" + "description": "Slug of the secure_proxy use case in the same integration." } } }, - "ManagedCallOperation": { + "FileProxyAuth": { "type": "object", "required": [ - "method", - "path" + "type", + "token_url", + "client_id", + "client_secret" ], - "description": "HTTP operation configuration for managed calls", "properties": { - "method": { + "type": { "type": "string", "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE" - ] + "oauth2_client_credentials", + "oauth2_password" + ], + "description": "Authentication type" }, - "path": { + "token_url": { "type": "string", - "description": "URL path template with {{variable}} interpolation" + "description": "Handlebars template for the OAuth2 token endpoint URL" + }, + "client_id": { + "type": "string", + "description": "Handlebars template for the OAuth2 client ID" + }, + "client_secret": { + "type": "string", + "description": "Handlebars template for the OAuth2 client secret" + }, + "scope": { + "type": "string", + "description": "Optional OAuth2 scope" + }, + "audience": { + "type": "string", + "description": "Handlebars template for the OAuth2 audience parameter" + }, + "resource": { + "type": "string", + "description": "Handlebars template for the OAuth2 resource parameter" + }, + "username": { + "type": "string", + "description": "Handlebars template for the OAuth2 resource owner username. Required when type is oauth2_password." + }, + "password": { + "type": "string", + "description": "Handlebars template for the OAuth2 resource owner password. Required when type is oauth2_password." + }, + "body_params": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Additional key-value pairs to include in the token request body. Values support Handlebars templates." }, "headers": { "type": "object", - "description": "Custom HTTP headers for the request. Values support {{variable}} interpolation from the request payload and {{env.VAR}} references for environment variables.", "additionalProperties": { "type": "string" - } + }, + "description": "Additional headers to include in the token request. Values support Handlebars templates." }, "query_params": { "type": "object", - "description": "Query parameters for the request. Values support {{variable}} interpolation from the request payload.", "additionalProperties": { "type": "string" - } + }, + "description": "Additional query parameters to append to the token URL. Values support Handlebars templates." } } }, - "ManagedCallExecuteRequest": { + "FileProxyParam": { "type": "object", "required": [ - "integration_id" + "name", + "required" ], "properties": { - "integration_id": { + "name": { "type": "string", - "format": "uuid", - "description": "Integration ID" + "description": "Parameter name as it appears in the query string" }, - "payload": { - "type": "object", - "additionalProperties": true, - "description": "Request payload for the managed call operation" + "required": { + "type": "boolean", + "description": "Whether this parameter is required" }, - "correlation_id": { + "description": { "type": "string", - "description": "Correlation ID for tracing related events (auto-generated if not provided)" - } - } - }, - "ManagedCallExecuteResponse": { - "description": "The response from a managed call execution.\nOn success, returns the JSONata-mapped response data directly (no wrapper).\nThe shape is entirely defined by your response_mapping JSONata expression.\nIf no response_mapping is configured, returns the raw external API response.\nCheck the X-Inbound-Event-Id header for inbound pipeline tracking when inbound routing is configured.\n", - "additionalProperties": true - }, - "ManagedCallErrorResponse": { - "type": "object", - "required": [ - "error" - ], - "properties": { - "error": { - "type": "object", - "required": [ - "code", - "message" - ], - "properties": { - "code": { - "type": "string", - "description": "Error code (e.g., EXTERNAL_API_ERROR, SSRF_BLOCKED, MANAGED_CALL_EXECUTION_ERROR)" - }, - "message": { - "type": "string", - "description": "Human-readable error message" - }, - "details": { - "type": "object", - "additionalProperties": true, - "description": "Additional error details (e.g., status code and body for external API errors)" - } - } - } - } - }, - "TypeAnnotations": { - "type": "object", - "description": "Developer-provided type annotations for a use case's request and response fields", - "properties": { - "request": { - "type": "object", - "description": "Type annotations for request fields, keyed by dot-path (e.g., \"vendors[].id\" -> \"string\")", - "additionalProperties": { - "type": "string" - } - }, - "response": { - "type": "object", - "description": "Type annotations for response fields", - "additionalProperties": { - "type": "string" - } + "description": "Human-readable description of the parameter" } } }, - "TypeDescriptor": { + "FileProxyStep": { "type": "object", - "description": "Describes the inferred type shape of a JSONata expression", "required": [ - "kind" + "url", + "method", + "response_type" ], "properties": { - "kind": { + "url": { + "type": "string", + "description": "Handlebars template for the request URL" + }, + "method": { "type": "string", "enum": [ - "object", - "array", - "string", - "number", - "boolean", - "null", - "unknown", - "union" - ] + "GET", + "POST", + "PUT", + "PATCH" + ], + "description": "HTTP method" }, - "properties": { + "headers": { "type": "object", - "description": "For kind=object, the properties and their type descriptors", "additionalProperties": { - "$ref": "#/components/schemas/TypeDescriptor" - } + "type": "string" + }, + "description": "Handlebars templates for request headers" }, - "items": { - "description": "For kind=array, the type of array items", - "allOf": [ - { - "$ref": "#/components/schemas/TypeDescriptor" - } - ] + "body_jsonata": { + "type": "string", + "description": "Upload-only. JSONata producing the request body as DATA; the result is serialized to\nJSON and sent. Use this for every JSON body — it cannot emit malformed JSON, and it\nomits a key whose value is undefined instead of sending it empty, which is what makes\noptional fields work without a conditional guard.\n\nThe evaluation root is the hydrated event, so `contact.customer_pin` and\n`ticket._purpose` are reachable directly, unprefixed. **Everything else is a\n`$`-prefixed binding**: `$file_data` (the files this delivery carries — see\n`FileProxyFanOutConfig` for what it holds in each mode), `$ack_id`, `$env`,\n`$steps` (results of the steps already executed, each\n`{statusCode, headers, body}`), `$germanDate(iso)` and `$now()`.\nWriting `file_data[0].filename` instead of `$file_data[0].filename` yields\nnothing — it reads a field named `file_data` on the event, which does not exist.\n\nLeave this empty to send the delivery's files exactly as they are: the single\nattachment object when fanning out, the whole array when not. No mapping is\nneeded for the common case.\n\nMust evaluate to an object or an array. Anything else fails the delivery terminally\nwith `MAPPING_EXPRESSION_FAILED`, naming the step.\n\n**Only `undefined` omits a key.** `null`, `\"\"`, `false` and `0` are values and are\nall sent. Two traps follow from that, and one idiom avoids both:\n\n- `x ? $string(x)` omits the key when `x` is `0` or `\"\"`, because JSONata reads both\n as false. A meter number of `\"0\"` would silently vanish.\n- `: undefined` is not a literal — JSONata has no `undefined` keyword, so it is a\n path lookup that happens to find nothing. Against an event that really does carry\n a field named `undefined`, it returns that field's value.\n\nWrite the **two-arm ternary with no else branch**, which omits the key when the test\nis false: `{ \"pin\": $exists(contact.pin) ? $string(contact.pin) }`.\n\nEvery `$` binding is checked at save time against the known set, so a mistyped\nbinding (`$lookup.documentType` for `$file_data[0].filename`) is rejected rather\nthan silently producing a missing key.\n\nNot accepted together with `body`, which the upload direction rejects outright.\n", + "example": "{ \"documentType\": \"Zählerstand\", \"fileName\": $file_data[0].filename, \"fileData\": $file_data[0].base64, \"pin\": $exists(contact.customer_pin) ? $string(contact.customer_pin) }" }, - "source": { + "enabled": { "type": "string", - "description": "For kind=unknown, the JSONata path that produced this value" + "description": "Upload-only, OPTIONAL. JSONata returning a boolean, deciding whether this step\nruns at all. Absent means it runs.\n\nA false result is a BREAK: this step is skipped and so is every step after it,\nand the delivery is recorded as `skipped` rather than delivered or failed. It is\nacknowledged and never retried, and a `STEP_DISABLED` monitoring event is emitted\nat level `info` — a disabled step is the configuration working, not a fault.\n\nReads the same bindings a body does, `$steps` included, so it can branch on what\nan earlier step returned. This is how a delivery is filtered out: with one\ndelivery per attachment, a false result on the first step drops that file.\n\nAn expression that throws, or returns a non-boolean, is a terminal\n`MAPPING_EXPRESSION_FAILED` instead — a broken predicate must not read as a\ndeliberate skip.\n", + "example": "$file_data[0].mime_type = \"application/pdf\"" }, - "variants": { - "type": "array", - "description": "For kind=union, the variant types", - "items": { - "$ref": "#/components/schemas/TypeDescriptor" - } + "body": { + "type": "string", + "description": "Handlebars template for the request body (write methods only), for bodies that are\nNOT JSON — form-encoded, XML, plain text. For a JSON body use `body_jsonata`\ninstead; it cannot produce malformed JSON.\n\n**Download-only.** The upload direction rejects it: use `body_jsonata`, or leave\nthat empty to send the files unchanged.\n\nRendered once, against the context described on `FileProxyUseCaseConfiguration`:\n`{{ params.* }}`, `{{ env.* }}`, `{{ steps.N.body }}`, `{{ auth_token }}`.\n\nIf a JSON body is written here anyway, **route every user-controlled value through\nthe `json` helper**: the template engine does not escape, so\n`\"name\":\"{{ params.documentName }}\"` produces invalid JSON the moment a value\ncontains a quote. Write `\"name\": {{json params.documentName}}` instead — the helper\nemits the surrounding quotes itself and renders absent values as `null`.\n\nMutually exclusive with `body_jsonata`, which is upload-only.\n" + }, + "response_type": { + "type": "string", + "enum": [ + "json", + "binary" + ], + "description": "Expected response type" } } }, - "UseCaseTypePreview": { + "FileProxyFanOutConfig": { "type": "object", - "description": "Scaffolded type descriptors for a single use case", "required": [ - "slug", - "request_shape", - "response_shape" + "enabled" ], + "description": "Whether one event produces one delivery per file, or a single delivery carrying all of\nthem. The split is always over the event's `event_attachments` — there is no expression\nto write, because an upload only ever runs on events that declare that field.\n\nEach resulting delivery is fully independent: its own idempotency record, its own retry\nschedule, its own monitoring events. A four-file event can therefore end up three-of-four\ndelivered, which is the honest state to report.\n\nThe split is evaluated ONCE, when the event is enqueued, so item indices — and therefore\nidempotency keys — stay stable across retries.\n\nEither way, expressions read the same `$file_data` binding, so no expression needs to\nknow which mode it runs in. An event carrying no attachments is recorded as `skipped`.\n", "properties": { - "slug": { - "type": "string" - }, - "name": { - "type": "string" - }, - "request_shape": { - "$ref": "#/components/schemas/TypeDescriptor" + "enabled": { + "type": "boolean", + "description": "When true, one delivery per attachment, and `$file_data` holds exactly that one\nfile. When false (or absent), the event produces exactly one delivery and\n`$file_data` holds every attachment on the event.\n\nAn empty `event_attachments` produces no deliveries and one info-level\n`FAN_OUT_EMPTY`, which is the normal outcome for a catch-all subscription seeing an\nevent with nothing to send.\n" + } + } + }, + "FileProxyUploadConfig": { + "type": "object", + "description": "Upload-side settings for a file_proxy use case with `direction: upload`.\nThe surrounding file_proxy configuration owns WHAT and HOW to send: `fan_out`,\nauth, and the steps with their `body_jsonata`. This nested object governs\nupload-specific limits and how the final external response is judged. The\noutbound mapping remains a pure pointer to the recipe (see `FileProxyDeliveryConfig`).\n", + "properties": { + "max_total_bytes": { + "type": "integer", + "description": "Ceiling for ALL of a delivery's files together, in bytes. Capped by the platform\nlimit, which it may lower but never raise.\n\nOnly reachable with `fan_out.enabled: false`, where one delivery carries every\nattachment and the worker holds them all resident at once. base64 inflates each\nby about a third, so the combined figure is what matters rather than any single\nfile's size.\n", + "example": 26214400 }, - "response_shape": { - "$ref": "#/components/schemas/TypeDescriptor" + "max_file_bytes": { + "type": "integer", + "minimum": 1, + "maximum": 104857600, + "description": "Per-file ceiling for this use case, in bytes. Files above it fail terminally with\n`FILE_TOO_LARGE` before any bytes are fetched. Defaults to — and is clamped by — the\nplatform ceiling of 100 MiB (104857600).\n" }, - "existing_annotations": { - "$ref": "#/components/schemas/TypeAnnotations" + "max_delivery_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 8, + "description": "Maximum delivery attempts per item before the delivery is marked failed. Attempts\nare spaced by an exponential, jittered backoff. The default 8 attempts schedule at\nmost 7 delays, totaling about 7 hours 40 minutes before jitter, so a normal ERP\nmaintenance window does not immediately exhaust them.\n\nLives here rather than on the outbound delivery because how hard to retry is a\nproperty of the transport — the same judgement as `max_file_bytes` — and the outbound\ndelivery is a pure pointer.\n" } } }, - "GenerateTypesPreviewResponse": { + "FileProxyResponseConfig": { "type": "object", + "description": "How to extract the file from the step results. REQUIRED when `direction` is `download`;\nrejected when `direction` is `upload` (an upload has no file to extract).\n", "required": [ - "use_cases" + "body", + "encoding" ], "properties": { - "integration_name": { - "type": "string" - }, - "use_cases": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UseCaseTypePreview" - } - }, - "previous_version": { + "body": { "type": "string", - "description": "Previously generated version, if any" + "description": "JSONata expression to extract file content from step results" }, - "suggested_version": { + "encoding": { "type": "string", - "description": "Suggested next version based on detected changes" + "enum": [ + "base64", + "binary" + ], + "description": "Encoding of the extracted body" }, - "suggested_bump": { + "filename": { "type": "string", - "enum": [ - "major", - "minor" - ] + "description": "JSONata expression to extract the filename" }, - "detected_changes": { - "type": "array", - "items": { - "type": "object", - "required": [ - "slug", - "field", - "type", - "description" - ], - "properties": { - "slug": { - "type": "string" - }, - "field": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "breaking", - "non-breaking" - ] - }, - "description": { - "type": "string" - } - } - } + "content_type": { + "type": "string", + "description": "JSONata expression to extract the content type" } } }, - "GenerateTypesRequest": { + "MappingSimulationRequest": { "type": "object", "required": [ - "package_name", - "version" + "mapping_configuration", + "object_type", + "format", + "payload" ], "properties": { - "package_name": { - "type": "string", - "description": "npm package name", - "example": "@epilot/hems-cleverpv" + "mapping_configuration": { + "oneOf": [ + { + "$ref": "#/components/schemas/IntegrationConfigurationV1" + }, + { + "$ref": "#/components/schemas/IntegrationConfigurationV2" + } + ] }, - "version": { + "object_type": { "type": "string", - "description": "Package version", - "example": "1.0.0" - }, - "description": { - "type": "string" + "description": "Type of the object/event being mapped.\nFor v1.0: must match a key in mapping_configuration.mapping.objects\nFor v2.0: must match a key in mapping_configuration.mapping.events\n" }, - "domain_package": { + "format": { "type": "string", - "description": "Domain standard package to extend", - "example": "@epilot/hems" + "enum": [ + "json", + "xml" + ], + "default": "json", + "description": "Format of the payload data" }, - "domain_map_name": { + "payload": { + "oneOf": [ + { + "type": "string", + "description": "The serialized object data payload (JSON, XML, etc.) as a string" + }, + { + "type": "object", + "description": "Direct JSON object (will be automatically serialized)", + "additionalProperties": true + } + ], + "description": "The object data payload - can be either a serialized string or a direct JSON object" + } + } + }, + "MappingSimulationV2Request": { + "type": "object", + "description": "Request for v2 mapping simulation. Uses the same configuration format stored in integration use case resources,\nmaking it easier to test configurations before saving them.\n", + "required": [ + "event_configuration", + "format", + "payload" + ], + "properties": { + "event_configuration": { + "$ref": "#/components/schemas/InboundIntegrationEventConfiguration" + }, + "format": { "type": "string", - "description": "Name of the domain UseCaseMap to extend", - "example": "HemsUseCaseMap" + "enum": [ + "json", + "xml" + ], + "default": "json", + "description": "Format of the payload data" }, - "annotations": { - "type": "object", - "description": "Type annotations per use case slug", - "additionalProperties": { - "$ref": "#/components/schemas/TypeAnnotations" - } + "payload": { + "oneOf": [ + { + "type": "string", + "description": "The serialized event data payload (JSON, XML, etc.) as a string" + }, + { + "type": "object", + "description": "Direct JSON object (will be automatically serialized)", + "additionalProperties": true + } + ], + "description": "The event data payload - can be either a serialized string or a direct JSON object" } } }, - "GenerateTypesResponse": { + "MappingSimulationResponse": { "type": "object", "required": [ - "package_name", - "version", - "files" + "entity_updates" ], "properties": { - "package_name": { - "type": "string" + "entity_updates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityUpdate" + } }, - "version": { - "type": "string" + "meter_readings_updates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MeterReadingUpdate" + } }, - "files": { - "type": "object", - "description": "Generated file contents keyed by file path", - "additionalProperties": { - "type": "string" + "prune_scope_updates": { + "type": "array", + "description": "Resolved prune scopes produced by entity mappings using `upsert-prune-scope-purge` / `upsert-prune-scope-delete`. Each entry describes the entities that would be deleted at runtime because they exist in scope but were not part of this payload.", + "items": { + "$ref": "#/components/schemas/EntityPruneScopeUpdate" + } + }, + "meter_readings_prune_scope_updates": { + "type": "array", + "description": "Resolved prune scopes produced by meter reading mappings using `upsert-prune-scope`. Each entry describes the readings that would be deleted at runtime for a meter (+ counter) because they were not part of this payload.", + "items": { + "$ref": "#/components/schemas/MeterReadingPruneScopeUpdate" } }, "warnings": { "type": "array", + "description": "Validation warnings about the configuration (e.g., unique_ids referencing non-indexed fields)", "items": { - "type": "string" + "$ref": "#/components/schemas/MappingSimulationWarning" } } } }, - "CommitTypesRequest": { + "MappingSimulationWarning": { "type": "object", "required": [ - "package_name", - "version" + "entity_schema", + "field", + "message" ], "properties": { - "package_name": { + "entity_schema": { "type": "string", - "description": "npm package name", - "example": "@epilot/hems-cleverpv" + "description": "The entity schema slug where the issue was found" }, - "version": { + "field": { "type": "string", - "description": "Package version", - "example": "1.0.0" + "description": "The field name that caused the warning" }, - "annotations": { - "type": "object", - "description": "Type annotations per use case slug", - "additionalProperties": { - "$ref": "#/components/schemas/TypeAnnotations" - } + "message": { + "type": "string", + "description": "Description of the validation issue" } } }, - "CommitTypesResponse": { + "DirectPayload": { "type": "object", + "description": "The payload of an ERP event routed to a `direct: true` use case: a versioned envelope of\npre-mapped operations that skip the mapping engine. Sent as the `payload` of an\n`ErpEventV3` - either as a JSON object or as a JSON string (`format` must be `json`;\nXML is rejected for direct use cases).\n\nThe schema is strict: unknown keys on the envelope or on any operation are rejected with\nan error naming the offending path. Attribute values, by contrast, pass through to the\nentity verbatim (except `$relation` / `$relation_ref` envelopes, which are validated and\nresolved).\n\nOne event lands as one queue message, so the whole event (including this payload) must\nstay within the 256 KiB message budget. Identical consecutive payloads within 5 minutes\ncan be deduplicated by the queue - use the event's `deduplication_id` deliberately when\nre-sending identical data.\n", "required": [ - "committed" + "version", + "operations" ], "properties": { - "committed": { - "type": "boolean" + "version": { + "type": "string", + "description": "Version of the direct payload contract. Currently only `\"1\"` is supported; an\nunsupported version is rejected with `DIRECT_VERSION_UNSUPPORTED`. Deliberately not\nan enum: the constraint is enforced by the handler so the dry-run endpoint returns\na structured `valid: false` verdict instead of a schema-level 400.\n" }, - "warnings": { + "operations": { "type": "array", + "description": "The operations to apply, in order. At most 100 operations per event (enforced by\nthe handler, with a structured error naming the limit). Each item is either an\nentity operation (`type` omitted or `\"entity\"`) or a meter reading operation\n(`type: \"meter_reading\"`).\n", "items": { - "type": "string" + "anyOf": [ + { + "$ref": "#/components/schemas/DirectEntityOperation" + }, + { + "$ref": "#/components/schemas/DirectMeterReadingOperation" + } + ] } } } }, - "FileProxyUseCaseConfiguration": { + "DirectEntityOperation": { "type": "object", + "description": "A single pre-mapped entity operation: upserts, soft-deletes, or purges one entity,\nidentified by its unique ids. Produces the same internal update as a mapped-mode entity\nconfiguration, so create-vs-update resolution, relation resolution, ordering, and\nmonitoring behave identically.\n", "required": [ - "steps", - "response" + "entity_slug", + "unique_ids" ], - "description": "Configuration for file_proxy use cases. Defines how to authenticate and fetch files from external document systems.\n\nThe file proxy download URL always requires `orgId`, `integrationId`, and either `useCaseSlug` (recommended) or `useCaseId` (legacy UUID) as query parameters.\nThe `orgId` is included in the signed URL to establish organization context without requiring authentication.\nAdditional use-case-specific parameters are declared in the `params` array.\n", + "additionalProperties": false, "properties": { - "secure_proxy": { - "description": "Optional secure proxy attachment for routing all outbound file proxy requests.\nOnly `use_case_slug` is supported and the referenced secure_proxy use case\nmust belong to the same integration.\n", - "allOf": [ - { - "$ref": "#/components/schemas/FileProxySecureProxyAttachment" - } - ] + "type": { + "type": "string", + "enum": [ + "entity" + ], + "description": "Operation discriminator. Optional - an operation without `type` is an entity operation." }, - "auth": { - "$ref": "#/components/schemas/FileProxyAuth" + "entity_slug": { + "type": "string", + "minLength": 1, + "description": "Target entity schema slug (e.g. `contact`, `contract`).", + "example": "contract" }, - "params": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FileProxyParam" + "unique_ids": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] }, - "description": "Additional use-case-specific parameters expected in the download URL query string (beyond the required orgId, integrationId, and useCaseSlug or useCaseId)" + "description": "Attribute values that uniquely identify this entity, as `{ attribute: value }`.\nValues may be strings or numbers (numbers are coerced to strings); values that are\nempty after trimming are rejected. Use `_id` as the sole key to reference an entity\ndirectly by its epilot id.\n\nUnlike mapped mode, the unique-id ATTRIBUTE NAMES are given directly - there is no\nfield-to-attribute mapping step.\n", + "example": { + "contract_number": "CTR-001" + } }, - "allowed_origins": { - "type": "array", - "items": { + "unique_id_types": { + "type": "object", + "additionalProperties": { "type": "string", - "format": "uri", - "pattern": "^https?://" + "enum": [ + "email", + "phone" + ] }, - "description": "Additional origins permitted to call /download for this use case (CORS, exact match). Portal origins are always allowed." + "description": "Optional per-field type hints for repeatable unique-id fields (`email` / `phone`),\nneeded so lookups search the repeatable storage format correctly. When omitted, the\nserver derives the types from the entity schema; explicit values always win over\nderivation.\n" }, - "steps": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/components/schemas/FileProxyStep" - }, - "description": "Ordered list of HTTP steps to execute to retrieve the file" + "mode": { + "type": "string", + "enum": [ + "upsert", + "delete", + "purge" + ], + "default": "upsert", + "description": "Operation mode: `upsert` creates or updates the entity (default), `delete` soft\ndeletes it, `purge` removes it permanently. For `delete` / `purge`, `attributes`\nmay be omitted - only `unique_ids` are used to locate the entity.\n" }, - "response": { - "$ref": "#/components/schemas/FileProxyResponseConfig" + "attributes": { + "type": "object", + "description": "Attribute values to write, keyed by attribute name. Required when `mode` is `upsert`\n(an empty object is allowed); optional for `delete` / `purge`.\n\nValues are written verbatim - the server does not validate them against the entity\nschema (attributes not defined in the schema are stored but not indexed; the\nsimulation endpoint surfaces warnings for unique-id fields missing from the schema).\nTwo envelope forms receive special treatment: `$relation` values\n(`DirectRelationValue`) and `$relation_ref` values (`DirectRelationRefValue`) are\nvalidated and resolved to entity relations.\n", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/components/schemas/DirectRelationValue" + }, + { + "$ref": "#/components/schemas/DirectRelationRefValue" + }, + { + "description": "Any other JSON value - written to the entity attribute verbatim." + } + ] + } } } }, - "FileProxySecureProxyAttachment": { + "DirectMeterReadingOperation": { "type": "object", + "description": "A single pre-mapped meter reading operation: upserts or deletes one reading on a meter\n(and optionally a specific counter), identified by their unique ids. Produces the same\ninternal update as a mapped-mode meter reading configuration.\n", "required": [ - "use_case_slug" + "type", + "meter", + "attributes" ], "additionalProperties": false, "properties": { - "use_case_slug": { + "type": { "type": "string", - "description": "Slug of the secure_proxy use case in the same integration." + "enum": [ + "meter_reading" + ], + "description": "Operation discriminator. Required for meter reading operations." + }, + "meter": { + "type": "object", + "required": [ + "unique_ids" + ], + "additionalProperties": false, + "description": "Reference to the meter this reading belongs to.", + "properties": { + "unique_ids": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "Attribute values that uniquely identify the meter, as `{ attribute: value }`.", + "example": { + "meter_number": "M-001" + } + } + } + }, + "counter": { + "type": "object", + "required": [ + "unique_ids" + ], + "additionalProperties": false, + "description": "Optional reference to the meter counter (register) the reading belongs to. Required\nwhen `reading_matching` is `strict-date`.\n", + "properties": { + "unique_ids": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "Attribute values that uniquely identify the counter, as `{ attribute: value }`.", + "example": { + "obis_number": "1-0:1.8.0" + } + } + } + }, + "mode": { + "type": "string", + "enum": [ + "upsert", + "delete" + ], + "default": "upsert", + "description": "Operation mode: `upsert` creates or updates the reading (default), `delete` removes it." + }, + "reading_matching": { + "type": "string", + "enum": [ + "external_id", + "strict-date" + ], + "description": "Strategy for matching this reading against existing readings. `external_id` (the\ndefault) matches by the reading's `external_id`; `strict-date` matches by meter +\ncounter + direction + date and REQUIRES `counter`. Useful when readings originate in\nthe portal and are echoed back by the ERP with truncated timestamps.\n" + }, + "attributes": { + "type": "object", + "required": [ + "external_id", + "timestamp", + "source", + "value" + ], + "description": "The reading data. Additional keys (e.g. `direction`, `reason`, `read_by`, `status`,\n`metadata`) pass through to the metering service verbatim.\n", + "properties": { + "external_id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "description": "External identifier of the reading (numbers are coerced to strings)." + }, + "timestamp": { + "type": "string", + "description": "When the reading was taken. ISO 8601 - either `YYYY-MM-DD` or\n`YYYY-MM-DDTHH:mm:ss` (optional fractional seconds and `Z` / `±HH:mm` offset).\nOther formats (e.g. `DD.MM.YYYY`, epoch numbers) are rejected.\n", + "example": "2026-08-24T06:00:00Z" + }, + "source": { + "type": "string", + "enum": [ + "ECP", + "ERP", + "360", + "journey-submission" + ], + "description": "Origin system of the reading." + }, + "value": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "description": "The reading value - a number, or a numeric string (coerced to a number)." + } + } + } + } + }, + "DirectRelationValue": { + "type": "object", + "description": "An entity attribute value that establishes relations to other entities. Written on any\nattribute inside a direct entity operation's `attributes`.\n\nTwo forms are accepted: a bare array of items (shorthand for `_set`, i.e. replace), or\nan object with EXACTLY ONE of the operations `_set` (replace), `_append` (merge,\ndeduplicated by entity id), `_append_all` (merge without deduplication). No other\noperation keys are accepted.\n", + "required": [ + "$relation" + ], + "additionalProperties": false, + "properties": { + "$relation": { + "oneOf": [ + { + "type": "array", + "description": "Shorthand for `_set` - replaces the attribute's relations with these items.", + "items": { + "$ref": "#/components/schemas/DirectRelationItem" + } + }, + { + "type": "object", + "description": "Exactly one of `_set`, `_append`, `_append_all` must be given.", + "additionalProperties": false, + "properties": { + "_set": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectRelationItem" + } + }, + "_append": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectRelationItem" + } + }, + "_append_all": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectRelationItem" + } + } + } + } + ] } } }, - "FileProxyAuth": { + "DirectRelationItem": { "type": "object", - "required": [ - "type", - "token_url", - "client_id", - "client_secret" - ], + "description": "One relation target: either an already-resolved reference (`entity_id`), or a lookup by\nunique ids (`schema` + `unique_ids`, resolved by the pipeline like any other unique-id\nlookup, including stub creation and retry when the target does not exist yet). The two\nforms are mutually exclusive. Use `_id` as the sole `unique_ids` key for a direct-id\nlookup.\n", + "additionalProperties": false, "properties": { - "type": { - "type": "string", - "enum": [ - "oauth2_client_credentials", - "oauth2_password" - ], - "description": "Authentication type" - }, - "token_url": { - "type": "string", - "description": "Handlebars template for the OAuth2 token endpoint URL" - }, - "client_id": { - "type": "string", - "description": "Handlebars template for the OAuth2 client ID" - }, - "client_secret": { - "type": "string", - "description": "Handlebars template for the OAuth2 client secret" - }, - "scope": { - "type": "string", - "description": "Optional OAuth2 scope" - }, - "audience": { - "type": "string", - "description": "Handlebars template for the OAuth2 audience parameter" - }, - "resource": { - "type": "string", - "description": "Handlebars template for the OAuth2 resource parameter" - }, - "username": { + "entity_id": { "type": "string", - "description": "Handlebars template for the OAuth2 resource owner username. Required when type is oauth2_password." + "description": "Epilot entity id of the relation target. Must not be combined with `schema` / `unique_ids`." }, - "password": { + "schema": { "type": "string", - "description": "Handlebars template for the OAuth2 resource owner password. Required when type is oauth2_password." + "description": "Entity schema slug of the relation target. Required when `entity_id` is not given.", + "example": "contact" }, - "body_params": { + "unique_ids": { "type": "object", + "minProperties": 1, "additionalProperties": { - "type": "string" + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] }, - "description": "Additional key-value pairs to include in the token request body. Values support Handlebars templates." + "description": "Attribute values that uniquely identify the target. Required when `entity_id` is not given.", + "example": { + "customer_number": "CUST-12345" + } }, - "headers": { + "unique_id_types": { "type": "object", "additionalProperties": { - "type": "string" + "type": "string", + "enum": [ + "email", + "phone" + ] }, - "description": "Additional headers to include in the token request. Values support Handlebars templates." + "description": "Optional per-field type hints for repeatable unique-id fields, as on the entity operation." }, - "query_params": { - "type": "object", - "additionalProperties": { + "tags": { + "type": "array", + "items": { "type": "string" }, - "description": "Additional query parameters to append to the token URL. Values support Handlebars templates." + "description": "Optional relation tags (e.g. `primary`, `billing`)." } } }, - "FileProxyParam": { + "DirectRelationRefValue": { "type": "object", + "description": "An entity attribute value that references an ITEM INSIDE a repeatable attribute of\nanother entity - for example one address out of a contact's `address` list. The target\nentity is looked up by unique ids, then the item at `path` matching `value` is\nreferenced (creating it when missing).\n\nThe same operation envelope as `$relation` applies: a bare array is shorthand for\n`_set`, otherwise exactly one of `_set`, `_append`, `_append_all`.\n", "required": [ - "name", - "required" + "$relation_ref" ], + "additionalProperties": false, "properties": { - "name": { - "type": "string", - "description": "Parameter name as it appears in the query string" - }, - "required": { - "type": "boolean", - "description": "Whether this parameter is required" - }, - "description": { - "type": "string", - "description": "Human-readable description of the parameter" + "$relation_ref": { + "oneOf": [ + { + "type": "array", + "description": "Shorthand for `_set`.", + "items": { + "$ref": "#/components/schemas/DirectRelationRefItem" + } + }, + { + "type": "object", + "description": "Exactly one of `_set`, `_append`, `_append_all` must be given.", + "additionalProperties": false, + "properties": { + "_set": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectRelationRefItem" + } + }, + "_append": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectRelationRefItem" + } + }, + "_append_all": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectRelationRefItem" + } + } + } + } + ] } } }, - "FileProxyStep": { + "DirectRelationRefItem": { "type": "object", + "description": "One relation-ref target - the entity to look up, and the repeatable item to reference on it.", "required": [ - "url", - "method", - "response_type" + "schema", + "unique_ids", + "path", + "value" ], + "additionalProperties": false, "properties": { - "url": { - "type": "string", - "description": "Handlebars template for the request URL" - }, - "method": { + "schema": { "type": "string", - "enum": [ - "GET", - "POST" - ], - "description": "HTTP method" + "minLength": 1, + "description": "Entity schema slug of the target entity.", + "example": "contact" }, - "headers": { + "unique_ids": { "type": "object", + "minProperties": 1, "additionalProperties": { - "type": "string" + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] }, - "description": "Handlebars templates for request headers" - }, - "body": { - "type": "string", - "description": "Handlebars template for the request body (POST only)" - }, - "response_type": { - "type": "string", - "enum": [ - "json", - "binary" - ], - "description": "Expected response type" - } - } - }, - "FileProxyResponseConfig": { - "type": "object", - "required": [ - "body", - "encoding" - ], - "properties": { - "body": { - "type": "string", - "description": "JSONata expression to extract file content from step results" - }, - "encoding": { - "type": "string", - "enum": [ - "base64", - "binary" - ], - "description": "Encoding of the extracted body" - }, - "filename": { - "type": "string", - "description": "JSONata expression to extract the filename" - }, - "content_type": { - "type": "string", - "description": "JSONata expression to extract the content type" - } - } - }, - "MappingSimulationRequest": { - "type": "object", - "required": [ - "mapping_configuration", - "object_type", - "format", - "payload" - ], - "properties": { - "mapping_configuration": { - "oneOf": [ - { - "$ref": "#/components/schemas/IntegrationConfigurationV1" - }, - { - "$ref": "#/components/schemas/IntegrationConfigurationV2" - } - ] + "description": "Attribute values that uniquely identify the target entity." }, - "object_type": { - "type": "string", - "description": "Type of the object/event being mapped.\nFor v1.0: must match a key in mapping_configuration.mapping.objects\nFor v2.0: must match a key in mapping_configuration.mapping.events\n" + "unique_id_types": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "email", + "phone" + ] + }, + "description": "Optional per-field type hints for repeatable unique-id fields." }, - "format": { + "path": { "type": "string", - "enum": [ - "json", - "xml" - ], - "default": "json", - "description": "Format of the payload data" + "minLength": 1, + "description": "The attribute on the TARGET entity that holds the repeatable array (e.g. `address`)." }, - "payload": { - "oneOf": [ - { - "type": "string", - "description": "The serialized object data payload (JSON, XML, etc.) as a string" - }, - { - "type": "object", - "description": "Direct JSON object (will be automatically serialized)", - "additionalProperties": true - } - ], - "description": "The object data payload - can be either a serialized string or a direct JSON object" + "value": { + "description": "The item to match (or create) at `path` - e.g. an address object.", + "example": { + "street": "Main Street", + "city": "Berlin" + } } } }, - "MappingSimulationV2Request": { + "DirectSimulationRequest": { "type": "object", - "description": "Request for v2 mapping simulation. Uses the same configuration format stored in integration use case resources,\nmaking it easier to test configurations before saving them.\n", + "description": "Request for a direct-mode dry run: the `direct: true` use case configuration to test\nagainst, and the payload to validate and translate.\n", "required": [ "event_configuration", - "format", "payload" ], "properties": { "event_configuration": { - "$ref": "#/components/schemas/InboundIntegrationEventConfiguration" - }, - "format": { - "type": "string", - "enum": [ - "json", - "xml" - ], - "default": "json", - "description": "Format of the payload data" + "allOf": [ + { + "$ref": "#/components/schemas/InboundIntegrationEventConfiguration" + } + ], + "description": "The direct use case configuration to simulate against. `direct` must be `true`;\n`entities` acts as the optional allowlist exactly as at runtime. This is the same\nconfiguration format stored on the inbound use case resource, so a configuration can\nbe tested before saving it.\n" }, "payload": { "oneOf": [ { "type": "string", - "description": "The serialized event data payload (JSON, XML, etc.) as a string" + "description": "The direct payload as a JSON string (as it would be sent in the event's `payload`)." }, { - "type": "object", - "description": "Direct JSON object (will be automatically serialized)", - "additionalProperties": true + "$ref": "#/components/schemas/DirectPayload" } ], - "description": "The event data payload - can be either a serialized string or a direct JSON object" + "description": "The direct payload to validate - a `DirectPayload` object, or its JSON string form." } } }, - "MappingSimulationResponse": { + "DirectSimulationResponse": { "type": "object", + "description": "Result of a direct-mode dry run. `valid: false` responses list EVERY violation found\nacross all operations in `errors`. `valid: true` responses preview the internal updates\nthe pipeline would apply, plus non-blocking `warnings`.\n", "required": [ - "entity_updates" + "valid", + "errors" ], "properties": { - "entity_updates": { + "valid": { + "type": "boolean", + "description": "Whether the payload would be accepted by the ingest endpoint." + }, + "errors": { "type": "array", + "description": "All validation errors found. Empty when `valid` is `true`.", "items": { - "$ref": "#/components/schemas/EntityUpdate" + "$ref": "#/components/schemas/DirectSimulationError" } }, - "meter_readings_updates": { + "warnings": { "type": "array", + "description": "Non-blocking findings - e.g. unique-id fields not defined in the target entity\nschema (lookups would never match), or a failed server-side unique-id type\nderivation.\n", "items": { - "$ref": "#/components/schemas/MeterReadingUpdate" + "$ref": "#/components/schemas/MappingSimulationWarning" } }, - "warnings": { + "entity_updates": { "type": "array", - "description": "Validation warnings about the configuration (e.g., unique_ids referencing non-indexed fields)", + "description": "The translated internal entity updates, exactly as the pipeline would process them.\nOnly present when `valid` is `true`.\n", "items": { - "$ref": "#/components/schemas/MappingSimulationWarning" + "allOf": [ + { + "$ref": "#/components/schemas/EntityUpdate" + }, + { + "type": "object", + "properties": { + "unique_identifiers_metadata": { + "type": "object", + "description": "Per-field metadata for repeatable unique-id fields - explicit\n`unique_id_types` merged with server-side schema derivation. Absent when\nno field resolved to a repeatable type.\n", + "additionalProperties": { + "type": "object", + "properties": { + "fieldType": { + "type": "string", + "enum": [ + "email", + "phone" + ] + }, + "index": { + "type": "integer" + } + } + } + } + } + } + ] + } + }, + "meter_reading_updates": { + "type": "array", + "description": "The translated internal meter reading updates. Only present when `valid` is `true`.\n", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/MeterReadingUpdate" + }, + { + "type": "object", + "properties": { + "_config": { + "type": "object", + "description": "Per-reading pipeline configuration carried by the operation.", + "properties": { + "reading_matching": { + "type": "string", + "enum": [ + "external_id", + "strict-date" + ] + } + } + } + } + } + ] } } } }, - "MappingSimulationWarning": { + "DirectSimulationError": { "type": "object", + "description": "One validation error found during a direct-mode dry run.", "required": [ - "entity_schema", - "field", + "code", "message" ], "properties": { - "entity_schema": { - "type": "string", - "description": "The entity schema slug where the issue was found" - }, - "field": { + "code": { "type": "string", - "description": "The field name that caused the warning" + "enum": [ + "DIRECT_PAYLOAD_INVALID", + "DIRECT_VERSION_UNSUPPORTED", + "DIRECT_ENTITY_NOT_ALLOWED" + ], + "description": "Error class, matching the monitoring codes the ingest pipeline emits:\n`DIRECT_PAYLOAD_INVALID` (schema violation, JSON parse failure, or XML format),\n`DIRECT_VERSION_UNSUPPORTED` (unknown payload `version`),\n`DIRECT_ENTITY_NOT_ALLOWED` (entity or unique-id keys not permitted by the use\ncase's allowlist).\n" }, "message": { "type": "string", - "description": "Description of the validation issue" + "description": "Human-readable description, including the path of the offending element." + }, + "operation_index": { + "type": "integer", + "description": "Zero-based index of the operation the error refers to, when attributable to one." } } }, @@ -6608,7 +9489,8 @@ "required": [ "entity_slug", "unique_identifiers", - "attributes" + "attributes", + "mode" ], "properties": { "entity_slug": { @@ -6624,6 +9506,38 @@ "type": "object", "description": "Mapped attribute values", "additionalProperties": true + }, + "pricing": { + "type": "object", + "description": "Present when the entity mapping has a `pricing` block. Echoes the pricing configuration and the data extracted for it, so mapping authors can see what would be sent to the pricing service.", + "required": [ + "config", + "data" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": true, + "description": "The pricing configuration from the entity mapping" + }, + "data": { + "type": "array", + "description": "The pricing input data extracted from the payload", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "mode": { + "type": "string", + "enum": [ + "upsert", + "delete", + "purge" + ], + "description": "Effective operation mode applied to this entity at runtime. `upsert-prune-scope-purge` / `upsert-prune-scope-delete` configurations report `upsert` here, because the individual entities in the payload are upserted — the destructive part of those modes is reported separately in `prune_scope_updates`. For `delete` / `purge`, `attributes` are still mapped and returned but ignored at runtime: only `unique_identifiers` are used to locate the entity to remove." } } }, @@ -6631,7 +9545,8 @@ "type": "object", "required": [ "meter", - "attributes" + "attributes", + "mode" ], "properties": { "meter": { @@ -6661,6 +9576,121 @@ "type": "object", "additionalProperties": true, "description": "Meter reading attributes. Required: external_id, timestamp, source, value. `timestamp` must be ISO 8601 — either `YYYY-MM-DD` or `YYYY-MM-DDTHH:mm:ss` (with optional fractional seconds and optional `Z` / `±HH:mm` timezone offset); non-ISO formats (e.g. `DD.MM.YYYY` or epoch numbers) are rejected and must be converted upstream via a `jsonataExpression` (e.g. `$fromMillis(...)`). Date-only values are normalized to midnight UTC and offset-less date-times are anchored to UTC before being forwarded to the metering API. `source` must be one of: ECP, ERP, 360, journey-submission. `reason` (optional) must be one of: regular, irregular, last, first, meter_change, contract_change, meter_adjustment (or empty/null)." + }, + "mode": { + "type": "string", + "enum": [ + "upsert", + "delete" + ], + "description": "Effective operation mode applied to this reading at runtime. `upsert-prune-scope` configurations report `upsert` here — their destructive part is reported separately in `meter_readings_prune_scope_updates`." + } + } + }, + "EntityPruneScopeUpdate": { + "type": "object", + "description": "A resolved entity prune scope. At runtime, entities of `entity_slug` found within `scope` whose unique identifiers are not listed in `keep_unique_ids` are removed using `deletion_mode`.", + "required": [ + "entity_slug", + "scope", + "keep_unique_ids", + "deletion_mode" + ], + "properties": { + "entity_slug": { + "type": "string", + "description": "The entity type that would be pruned" + }, + "scope": { + "type": "object", + "description": "The scope configuration resolved against the payload", + "required": [ + "scope_mode" + ], + "properties": { + "scope_mode": { + "type": "string", + "enum": [ + "relations", + "query" + ], + "description": "How the entities in scope are found" + }, + "schema": { + "type": "string", + "description": "Schema of the related entity defining the scope (`relations` scope_mode only)" + }, + "unique_ids": { + "type": "object", + "additionalProperties": true, + "description": "Resolved unique identifiers of the related scope entity (`relations` scope_mode only)" + }, + "query": { + "type": "object", + "additionalProperties": true, + "description": "Resolved query parameters used to find entities in scope (`query` scope_mode only)" + } + } + }, + "keep_unique_ids": { + "type": "array", + "description": "Unique identifiers of the entities upserted by this payload. Everything else found in scope would be deleted.", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "deletion_mode": { + "type": "string", + "enum": [ + "delete", + "purge" + ], + "description": "`delete` soft deletes the pruned entities, `purge` removes them permanently" + } + } + }, + "MeterReadingPruneScopeUpdate": { + "type": "object", + "description": "A resolved meter reading prune scope. At runtime, readings of the given meter (+ counter) whose external ids are not listed in `keep_external_ids` are deleted.", + "required": [ + "meter", + "keep_external_ids" + ], + "properties": { + "meter": { + "type": "object", + "required": [ + "$entity_unique_ids" + ], + "properties": { + "$entity_unique_ids": { + "type": "object", + "additionalProperties": true, + "description": "Unique identifiers for the meter" + } + } + }, + "meter_counter": { + "type": "object", + "properties": { + "$entity_unique_ids": { + "type": "object", + "additionalProperties": true, + "description": "Unique identifiers for the meter counter" + } + } + }, + "keep_external_ids": { + "type": "array", + "description": "External ids of the readings present in this payload. All other readings in scope would be deleted. An empty array means every reading in scope would be deleted.", + "items": { + "type": "string" + } + }, + "source": { + "type": "string", + "description": "When set, only readings with this source are eligible for pruning (e.g. `ERP`)" } } }, @@ -6796,12 +9826,12 @@ "enabled", "delivery" ], - "description": "A mapping that delivers an event to an external system — either pushed to a webhook (with a JSONata payload transformation) or made available on the pull-based poll queue (raw event payload, no transformation)", + "description": "A mapping that delivers an event to an external system by one of three mechanisms — pushed to a webhook (with a JSONata payload transformation), made available on the pull-based poll queue (raw event payload, no transformation), or handed to a file_proxy use case that uploads files to an external document system (a pointer only; the referenced use case owns the payload and the fan-out)", "properties": { "id": { "type": "string", "format": "uuid", - "description": "Unique identifier for this mapping" + "description": "Unique identifier for this mapping; generated by the API when omitted" }, "name": { "type": "string", @@ -6810,7 +9840,7 @@ }, "jsonata_expression": { "type": "string", - "description": "JSONata expression to transform the event payload. Required for webhook delivery; ignored for poll delivery.", + "description": "JSONata expression to transform the event payload. Required for webhook delivery, ignored for poll delivery, and rejected for file_proxy delivery — a file_proxy payload is built by the referenced use case's steps, so accepting an expression here would silently do nothing.", "example": "{ \"id\": entity._id, \"customer\": entity.customer_name }" }, "enabled": { @@ -6834,20 +9864,24 @@ } }, "DeliveryConfig": { - "description": "Configuration for how the event should be delivered. webhook = push delivery via svc-webhooks (JSONata-transformed payload); poll = pull-based queue delivery where the consumer fetches items via the poll API (raw event payload)", + "description": "Configuration for how the event should be delivered. webhook = push delivery via svc-webhooks (JSONata-transformed payload); poll = pull-based queue delivery where the consumer fetches items via the poll API (raw event payload); file_proxy = one push per event attachment to an external document system, through a file_proxy use case (JSONata-transformed payload carrying the file bytes)", "oneOf": [ { "$ref": "#/components/schemas/WebhookDeliveryConfig" }, { "$ref": "#/components/schemas/PollDeliveryConfig" + }, + { + "$ref": "#/components/schemas/FileProxyDeliveryConfig" } ], "discriminator": { "propertyName": "type", "mapping": { "webhook": "#/components/schemas/WebhookDeliveryConfig", - "poll": "#/components/schemas/PollDeliveryConfig" + "poll": "#/components/schemas/PollDeliveryConfig", + "file_proxy": "#/components/schemas/FileProxyDeliveryConfig" } } }, @@ -6907,15 +9941,129 @@ "dead_letter", "block" ], - "default": "dead_letter", - "description": "What happens when an item exhausts max_delivery_attempts: dead_letter routes the exhausted item to the dead-letter queue; block halts the queue until operator/consumer action. Enforcement lands with the queue consumer (Phase 10) — this field defines the contract." + "default": "dead_letter", + "description": "What happens when an item exhausts max_delivery_attempts: dead_letter routes the exhausted item to the dead-letter queue and advances past it so the stream keeps flowing; block halts the queue at that item until operator/consumer action removes it." + }, + "max_delivery_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 5, + "description": "Maximum delivery attempts before the poison_policy is applied" + } + } + }, + "FileProxyDeliveryConfig": { + "type": "object", + "required": [ + "type", + "use_case_slug" + ], + "description": "Push delivery to an external document system through a `file_proxy` use case.\n\nA pure pointer, deliberately. The outbound use case decides WHEN to deliver — its event\nname plus `event_filter` — and the referenced `file_proxy` use case decides WHAT and HOW:\nwhich items to fan out over (`fan_out`), what values to build (each\nstep's `body_jsonata`), and the steps that send them. Keeping every file concern on the\nfile_proxy side is what lets one upload recipe be shared by several event subscriptions\nwithout duplicating any of it.\n\n`jsonata_expression` on the mapping is NOT used for this delivery type and is rejected.\n\nThe slug is resolved at delivery time, not at save time, so use cases can be saved in any\norder; a dangling reference surfaces as a `USE_CASE_NOT_FOUND` monitoring event naming\nthe referencing mapping.\n", + "properties": { + "type": { + "type": "string", + "enum": [ + "file_proxy" + ], + "description": "Delivery mechanism type" + }, + "use_case_slug": { + "type": "string", + "description": "Slug of a `file_proxy` use case with `direction: upload` in the SAME integration.\n", + "example": "wemag_d3_document_upload" + } + } + }, + "EntitySyncStatusResponse": { + "type": "object", + "required": [ + "entity_id", + "sync_states" + ], + "properties": { + "entity_id": { + "type": "string", + "format": "uuid", + "description": "The entity the sync states belong to" + }, + "sync_states": { + "type": "array", + "description": "One entry per integration that has processed an inbound event for the entity", + "items": { + "$ref": "#/components/schemas/EntitySyncState" + } + } + } + }, + "EntitySyncState": { + "type": "object", + "description": "Inbound sync state of one entity for one integration. Carries two\nindependent groups: the entity-level fields (`last_synced_at`, …) and,\non meter/meter_counter entities, the readings-level fields\n(`readings_last_synced_at`, …). Either group can be present alone — a\nmeter whose readings sync via the ERP but whose entity was never\nitself inbound-mapped carries only the readings group.\n", + "required": [ + "entity_id", + "integration_id", + "entity_slug" + ], + "properties": { + "entity_id": { + "type": "string", + "format": "uuid", + "description": "The entity ID" + }, + "integration_id": { + "type": "string", + "format": "uuid", + "description": "The integration that synchronized the entity" + }, + "entity_slug": { + "type": "string", + "description": "The entity schema slug", + "example": "contract" + }, + "use_case_id": { + "type": "string", + "description": "The inbound use case that last processed the entity" + }, + "last_synced_at": { + "type": "string", + "format": "date-time", + "description": "Last time an inbound ERP event for this entity was processed —\nincluding no-op checks that changed nothing on the entity.\n" + }, + "last_changed_at": { + "type": "string", + "format": "date-time", + "description": "Last time an inbound ERP event actually changed the entity (create/patch/delete)" + }, + "last_operation": { + "type": "string", + "enum": [ + "create", + "patch", + "delete", + "no-op" + ], + "description": "Outcome of the most recent inbound sync check" + }, + "last_event_id": { + "type": "string", + "description": "The event ID of the most recent inbound sync check" + }, + "readings_last_synced_at": { + "type": "string", + "format": "date-time", + "description": "Last time meter readings for this meter/meter_counter entity were\nreceived and processed from the ERP. Only present on meter and\nmeter_counter entities. The metering path cannot detect no-ops,\nso there is no separate readings changed-at.\n" + }, + "readings_last_operation": { + "type": "string", + "enum": [ + "upsert", + "delete" + ], + "description": "Outcome of the most recent inbound readings sync" }, - "max_delivery_attempts": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 5, - "description": "Maximum delivery attempts before the poison_policy is applied" + "readings_last_event_id": { + "type": "string", + "description": "The event ID of the most recent inbound readings sync" } } }, @@ -6989,6 +10137,60 @@ }, "poll": { "$ref": "#/components/schemas/OutboundPollStatus" + }, + "file_proxy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutboundFileProxyTargetStatus" + }, + "description": "One entry per file_proxy mapping on this use case. Present only on use cases with a\nfile_proxy delivery mapping.\n" + } + } + }, + "OutboundFileProxyTargetStatus": { + "type": "object", + "description": "Resolution state of one file_proxy mapping's referenced upload use case.\n\nBecause the transport lives on a separate use case, edits to the endpoint or credentials\ndo not appear in THIS use case's history. `target_updated_at` is the hook that lets an\noperator notice a target changed underneath a delivery that started failing.\n", + "required": [ + "mapping_id", + "use_case_slug", + "resolved" + ], + "properties": { + "mapping_id": { + "type": "string", + "description": "The mapping holding this file_proxy delivery" + }, + "use_case_slug": { + "type": "string", + "description": "Slug of the referenced file_proxy use case" + }, + "resolved": { + "type": "boolean", + "description": "Whether the slug currently resolves to an enabled `file_proxy` use case with\n`direction: upload` in this integration. References are resolved at delivery time,\nso `false` is a live warning rather than a save-time error.\n" + }, + "target_use_case_id": { + "type": "string", + "format": "uuid", + "description": "Id of the referenced use case — absent when unresolved" + }, + "target_enabled": { + "type": "boolean", + "description": "Whether the referenced use case is enabled — absent when unresolved" + }, + "target_updated_at": { + "type": "string", + "format": "date-time", + "description": "When the referenced use case was last modified — absent when unresolved. Surfaced\nbecause a change here is invisible in this use case's own history diff.\n" + }, + "unresolved_reason": { + "type": "string", + "enum": [ + "not_found", + "wrong_type", + "wrong_direction", + "disabled" + ], + "description": "Why the reference did not resolve — absent when `resolved` is true" } } }, @@ -8470,6 +11672,160 @@ } } }, + "ExternalMonitoringSpan": { + "type": "object", + "properties": { + "correlation_id": { + "type": "string", + "description": "Trace id — links this span to the epilot trace. Unique per business operation. Required." + }, + "level": { + "type": "string", + "description": "Span outcome level — one of success | error | warning | info. Drives the server-assigned EXTERNAL_* code, coloring, and alerting. Required.\n" + }, + "use_case_slug": { + "type": "string", + "description": "Business use case slug (e.g. \"business_partner\"). Resolved server-side against the integration's configured use cases to a use_case_id/use_case_type; stored as the grouping dimension. Portable — no epilot-internal UUIDs required.\n" + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "External clock time the span occurred; stored as created_at." + }, + "message": { + "type": "string", + "description": "Human-readable line shown in the trace and event tables." + }, + "detail": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Free-form context (step, http_status, reason, …). The place for external-system specificity — the taxonomy code stays clean (EXTERNAL_*).\n" + } + }, + "description": "A single monitoring span produced by an external system. `correlation_id`, `level`, `use_case_slug`, `occurred_at` and `message` are all required and `level` must be one of success|error|warning|info — but these are validated PER SPAN at ingest and reported in the per-item `results[]`, so one malformed span never fails the whole batch (the fields are intentionally not marked `required`/`enum` at the schema level to preserve that partial-acceptance behavior). The client does NOT send a `code` (server-assigned from `level`), a `source` (origin is the EXTERNAL_* code prefix), or span/event ids (the external system owns its own identity and dedup).\n" + }, + "IngestExternalMonitoringEventsRequest": { + "type": "object", + "required": [ + "events" + ], + "properties": { + "events": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "items": { + "$ref": "#/components/schemas/ExternalMonitoringSpan" + }, + "description": "Batch of external monitoring spans (max 100 per request)." + } + } + }, + "IngestExternalMonitoringEventsResponse": { + "type": "object", + "required": [ + "accepted", + "rejected" + ], + "properties": { + "accepted": { + "type": "integer", + "description": "Number of spans accepted and republished onto the monitoring bus." + }, + "rejected": { + "type": "integer", + "description": "Number of spans rejected in validation. See `results` for reasons." + }, + "results": { + "type": "array", + "description": "Per-span results; present for rejections in a mixed batch.", + "items": { + "type": "object", + "required": [ + "index", + "status" + ], + "properties": { + "index": { + "type": "integer", + "description": "Zero-based index of the span in the request `events` array." + }, + "status": { + "type": "string", + "enum": [ + "accepted", + "rejected" + ] + }, + "reason": { + "type": "string", + "description": "Rejection reason (present when status is rejected)." + } + } + } + } + } + }, + "MonitoringTraceResponse": { + "type": "object", + "required": [ + "correlation_id", + "status", + "span_count", + "truncated", + "spans" + ], + "properties": { + "correlation_id": { + "type": "string", + "description": "The trace id these spans share." + }, + "status": { + "type": "string", + "enum": [ + "success", + "error", + "warning", + "info" + ], + "description": "Rolled-up status across all spans (error > warning > success > info)." + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "created_at of the earliest span (null when the trace is empty)." + }, + "ended_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "created_at of the latest returned span (null when the trace is empty)." + }, + "span_count": { + "type": "integer", + "description": "Number of spans RETURNED (== spans.length; capped)." + }, + "truncated": { + "type": "boolean", + "description": "True when the trace has more spans than the cap; the returned window is the earliest spans, so ended_at/status reflect only that window.\n" + }, + "spans": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MonitoringEventV2" + }, + "description": "All spans sharing the correlation_id, chronological. External-origin spans carry an EXTERNAL_* code; epilot spans carry an epilot code or empty (success).\n" + }, + "inbound_event": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The epilot inbound event \"head\" (raw payload), when present." + } + } + }, "MonitoringEventV2": { "type": "object", "required": [ @@ -8574,7 +11930,21 @@ "managed_call", "secure_proxy" ], - "description": "Filter stats by use case type" + "description": "Filter stats by a single use case type" + }, + "use_case_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "inbound", + "outbound", + "file_proxy", + "managed_call", + "secure_proxy" + ] + }, + "description": "Filter stats to this set of use case types (matches any). Takes precedence over `use_case_type` when both are present. Used by the notification producers to scope alerts/digests to the integration's `monitoredUseCases`." }, "group_by": { "type": "string", @@ -8792,6 +12162,110 @@ } } } + }, + "DocumentationPageSummary": { + "type": "object", + "description": "Documentation page metadata without the markdown content", + "required": [ + "id", + "integration_id", + "scope", + "title", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "type": "string", + "description": "'general' for the integration-wide page, otherwise the use case ID", + "example": "general" + }, + "integration_id": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "scope": { + "type": "string", + "enum": [ + "integration", + "use_case" + ], + "description": "Whether the page documents the whole integration or a single use case" + }, + "use_case_id": { + "type": "string", + "format": "uuid", + "description": "The linked use case. Only set when scope is use_case." + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "created_by": { + "type": "string", + "nullable": true, + "description": "User ID that created the page", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_by": { + "type": "string", + "nullable": true, + "description": "User ID of the last edit", + "readOnly": true + } + } + }, + "DocumentationPage": { + "description": "A markdown documentation page of an integration", + "allOf": [ + { + "$ref": "#/components/schemas/DocumentationPageSummary" + }, + { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "maxLength": 100000, + "description": "Markdown source of the page" + } + } + } + ] + }, + "UpsertDocumentationPageRequest": { + "type": "object", + "required": [ + "title", + "content" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 150 + }, + "content": { + "type": "string", + "maxLength": 100000, + "description": "Markdown source of the page" + } + } } }, "responses": { @@ -8835,6 +12309,16 @@ } } }, + "Conflict": { + "description": "Conflict - the resource is not in a state that allows this operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBase" + } + } + } + }, "InternalServerError": { "description": "Internal Server Error", "content": { @@ -9014,7 +12498,7 @@ } }, "ReplayEventsResponse": { - "description": "Events replay initiated", + "description": "Events replay initiated. Always 200 — per-event outcomes are reported in `results`, so inspect it (or compare `replayed` against the number of requested ids) rather than treating the status code as success.\n", "content": { "application/json": { "schema": { @@ -9024,6 +12508,48 @@ "results" ], "properties": { + "replayed": { + "type": "integer", + "description": "Number of events actually queued for re-processing.", + "example": 2 + }, + "results": { + "type": "array", + "description": "One entry per requested event id, in request order.", + "items": { + "type": "object", + "required": [ + "event_id", + "status" + ], + "properties": { + "event_id": { + "type": "string", + "description": "The requested (source) event ID." + }, + "status": { + "type": "string", + "enum": [ + "success", + "queued", + "skipped", + "ignored", + "not_found", + "error" + ], + "description": "Outcome for this event. `success`/`queued` means it was enqueued for re-processing. `not_found` means no inbound event with that ID exists for the organization. `skipped` means it was deduplicated, `ignored` means no enabled use case matched it, and `error` means it could not be queued (see `message`).\n" + }, + "replay_event_id": { + "type": "string", + "description": "The new event ID assigned to the replayed event. Use it to follow the replay in monitoring.\n" + }, + "message": { + "type": "string", + "description": "Human-readable detail for this outcome." + } + } + } + }, "event_ids": { "type": "array", "items": { diff --git a/packages/cli/definitions/journey.json b/packages/cli/definitions/journey.json index 2777259d5..1b8a3fcaa 100644 --- a/packages/cli/definitions/journey.json +++ b/packages/cli/definitions/journey.json @@ -3,7 +3,7 @@ "info": { "title": "Journey API", "description": "API to configure journeys", - "version": "1.4.2" + "version": "1.4.3" }, "security": [ { @@ -149,7 +149,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Journey" + "allOf": [ + { + "$ref": "#/components/schemas/Journey" + }, + { + "$ref": "#/components/schemas/JourneyActivationGuarantee" + } + ] } } } @@ -200,6 +207,106 @@ } } }, + "/v1/journey/configuration/{id}/environment": { + "get": { + "operationId": "getJourneyEnvironment", + "summary": "getJourneyEnvironment", + "description": "Resolve the environment variables referenced by this journey. Only browser-safe value types are returned.", + "security": [ + { + "EpilotAuth": [] + } + ], + "tags": [ + "Journeys" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Journey ID bound to the supplied journey access token", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Resolved environment-backed datasources", + "headers": { + "Cache-Control": { + "schema": { + "type": "string" + }, + "description": "Always private, no-store" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JourneyEnvironmentResponse" + } + } + } + }, + "401": { + "description": "Journey access token missing" + }, + "403": { + "description": "Journey access token does not match the journey" + }, + "502": { + "description": "Environment resolver unavailable" + } + } + } + }, + "/v1/journey/environment-variables": { + "get": { + "operationId": "getJourneyEnvironmentVariables", + "summary": "getJourneyEnvironmentVariables", + "description": "List the organization's environment variables that a journey block may use as an options source. Only Map variables that currently hold a valid value are returned. Intended for the journey builder's authoring UI; requires a journey authoring token, not an environments token.", + "security": [ + { + "EpilotAuth": [] + } + ], + "tags": [ + "Journeys" + ], + "responses": { + "200": { + "description": "Map environment variables available as an options source", + "headers": { + "Cache-Control": { + "schema": { + "type": "string" + }, + "description": "Always private, no-store" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JourneyEnvironmentVariablesResponse" + } + } + } + }, + "401": { + "description": "Authorization token missing" + }, + "403": { + "description": "Not permitted to read this organization's journeys" + }, + "502": { + "description": "Environment resolver unavailable" + } + } + } + }, "/v1/journey/products/{id}": { "get": { "operationId": "getJourneyProducts", @@ -736,7 +843,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JourneyCreationRequestV2" + "allOf": [ + { + "$ref": "#/components/schemas/JourneyCreationRequestV2" + }, + { + "$ref": "#/components/schemas/JourneyActivationGuarantee" + } + ] } } } @@ -938,6 +1052,160 @@ }, "components": { "schemas": { + "EnvironmentMapEntry": { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + } + } + }, + "EnvironmentMap": { + "type": "object", + "required": [ + "options" + ], + "properties": { + "fallbackLanguage": { + "type": "string", + "default": "de" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EnvironmentMapEntry" + } + } + } + }, + "JourneyEnvironmentResponse": { + "type": "object", + "required": [ + "items", + "errors" + ], + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "datasourceId", + "type", + "value" + ], + "properties": { + "datasourceId": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "Text", + "Number", + "Boolean", + "Map" + ] + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "$ref": "#/components/schemas/EnvironmentMap" + } + ] + } + } + } + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "required": [ + "datasourceId", + "code" + ], + "properties": { + "datasourceId": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "not_found", + "unsupported_type", + "not_set", + "invalid_value", + "incompatible_consumer" + ] + } + } + } + } + } + }, + "JourneyEnvironmentVariablesResponse": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "type", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "Map" + ] + }, + "value": { + "$ref": "#/components/schemas/EnvironmentMap" + }, + "description": { + "type": "string" + } + } + } + } + } + }, "GetJourneysResponse": { "type": "object" }, @@ -1064,6 +1332,13 @@ "type": "boolean", "nullable": true }, + "stepperType": { + "type": "string", + "enum": [ + "numbers", + "progress bar" + ] + }, "hideNextButton": { "type": "boolean", "nullable": true @@ -1296,6 +1571,12 @@ "shouldLoadEntity": { "type": "boolean", "description": "If type is not text, we can instruct the journey to fetch the entity id we receive as value" + }, + "description": { + "type": "string", + "nullable": true, + "maxLength": 500, + "description": "Human-readable note describing the parameter's purpose. Free text; may contain newlines." } }, "required": [ @@ -1336,11 +1617,7 @@ }, "lang": { "type": "string", - "enum": [ - "de", - "en", - "fr" - ] + "example": "de" }, "width": { "type": "string" @@ -1382,7 +1659,8 @@ "type": "string" }, "templateId": { - "type": "string" + "type": "string", + "nullable": true }, "entityId": { "type": "string", @@ -1391,6 +1669,10 @@ "mappingsAutomationId": { "type": "string" }, + "newMappings": { + "description": "When true, the journey is created without a mapping config or automation; mappings are managed as advanced mappings on a lazily created automation.", + "type": "boolean" + }, "targetedCustomer": { "type": "string" }, @@ -1481,6 +1763,19 @@ "PRIVATE" ] }, + "authGate": { + "type": "object", + "description": "Steps after this step require an authenticated session (auth gate)", + "required": [ + "stepId" + ], + "properties": { + "stepId": { + "type": "string", + "description": "The step containing the Login & Registration block" + } + } + }, "isPublished": { "type": "boolean" }, @@ -1493,7 +1788,7 @@ "savingProgress": { "type": "object", "properties": { - "savingMode": { + "mode": { "type": "string", "enum": [ "auto", @@ -1502,7 +1797,7 @@ "none" ] }, - "supportedVersion": { + "supportedRevision": { "type": "number" } } @@ -1591,6 +1886,13 @@ "type": "boolean", "nullable": true }, + "stepperType": { + "type": "string", + "enum": [ + "numbers", + "progress bar" + ] + }, "hideNextButton": { "type": "boolean", "nullable": true @@ -1823,6 +2125,12 @@ "shouldLoadEntity": { "type": "boolean", "description": "If type is not text, we can instruct the journey to fetch the entity id we receive as value" + }, + "description": { + "type": "string", + "nullable": true, + "maxLength": 500, + "description": "Human-readable note describing the parameter's purpose. Free text; may contain newlines." } }, "required": [ @@ -1863,11 +2171,7 @@ }, "lang": { "type": "string", - "enum": [ - "de", - "en", - "fr" - ] + "example": "de" }, "width": { "type": "string" @@ -1910,8 +2214,13 @@ "mappingsAutomationId": { "type": "string" }, + "newMappings": { + "description": "When true, the journey is created without a mapping config or automation; mappings are managed as advanced mappings on a lazily created automation.", + "type": "boolean" + }, "templateId": { - "type": "string" + "type": "string", + "nullable": true }, "targetedCustomer": { "type": "string" @@ -1992,12 +2301,46 @@ "PRIVATE" ] }, + "authGate": { + "type": "object", + "description": "Steps after this step require an authenticated session (auth gate)", + "required": [ + "stepId" + ], + "properties": { + "stepId": { + "type": "string", + "description": "The step containing the Login & Registration block" + } + } + }, "enableDarkMode": { "type": "boolean", "description": "If true, the journey shows an icon to toggle dark mode" }, + "useAustrianLabels": { + "type": "boolean", + "description": "If true, some journey input labels are in Austrian format" + }, "isActive": { "type": "boolean" + }, + "savingProgress": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "auto", + "local", + "remote", + "none" + ] + }, + "supportedRevision": { + "type": "number" + } + } } }, "additionalProperties": false @@ -2169,6 +2512,27 @@ } ] }, + "JourneyActivationGuarantee": { + "description": "Journeys read from the API always carry an explicit `settings.isActive`. The flag remains optional in request bodies.\n", + "type": "object", + "required": [ + "settings" + ], + "properties": { + "settings": { + "type": "object", + "required": [ + "isActive" + ], + "properties": { + "isActive": { + "type": "boolean", + "description": "Whether the journey is active (accessible to end customers)" + } + } + } + } + }, "JourneyFeatureFlags": { "type": "object", "properties": { @@ -2534,6 +2898,11 @@ } } }, + "context_entity_id": { + "description": "Entity id used to resolve entity and relational template variables", + "type": "string", + "format": "uuid" + }, "language": { "description": "Language code for the document", "type": "string", @@ -2583,13 +2952,22 @@ }, "ValidationRuleRef": { "type": "object", - "description": "References to validation rules organized by blocks and fields.\nMaps block IDs to either rule IDs (for block-level rules) or rule references (for field-level rules).\n", + "description": "References to validation rules organized by blocks and fields.\nMaps block IDs to either one or more ordered rule IDs (for block-level rules)\nor rule references (for field-level rules).\n", "additionalProperties": { "oneOf": [ { "type": "string", "description": "Rule ID for block-level validation" }, + { + "type": "array", + "description": "Ordered rule IDs for block-level validation", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, { "$ref": "#/components/schemas/RuleRef" } @@ -2597,23 +2975,45 @@ }, "example": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": [ + "rule456", + "rule789" + ], + "block3": { + "field1": "rule101", + "field2": [ + "rule102", + "rule103" + ] } } }, "RuleRef": { "type": "object", - "description": "Field-level rule references within a block.\nMaps field names to rule IDs.\n", + "description": "Field-level rule references within a block.\nMaps field names to one or more ordered rule IDs.\n", "additionalProperties": { - "type": "string", - "description": "Rule ID for field-level validation" + "oneOf": [ + { + "type": "string", + "description": "Rule ID for field-level validation" + }, + { + "type": "array", + "description": "Ordered rule IDs for field-level validation", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + } + ] }, "example": { "firstName": "rule123", - "lastName": "rule456", - "email": "rule789" + "lastName": [ + "rule456", + "rule789" + ] } } }, diff --git a/packages/cli/definitions/message.json b/packages/cli/definitions/message.json index 20797c379..ff4361631 100644 --- a/packages/cli/definitions/message.json +++ b/packages/cli/definitions/message.json @@ -123,6 +123,34 @@ }, "403": { "description": "Forbidden" + }, + "409": { + "description": "Conflict - the draft was edited by someone else since it was loaded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Human-readable conflict message", + "example": "This draft was edited by someone else. Please reload and try again." + }, + "current_message": { + "description": "The current server version of the message (includes updated_by / _updated_at)", + "allOf": [ + { + "$ref": "#/components/schemas/BaseEntity" + }, + { + "$ref": "#/components/schemas/Message" + } + ] + } + } + } + } + } } } } @@ -553,6 +581,44 @@ } } }, + "/v1/message/unread:counts": { + "post": { + "operationId": "getUnreadCounts", + "summary": "getUnreadCounts", + "description": "Unread counts for several named scopes in one request.\n\nA scope is a name plus the same parameters the thread list already takes (`q`, `inbox_id`),\nso a scope's count and the list beneath it are the same predicate and agree by construction.\nThe server adds only the read-state condition; it does not re-author the caller's view.\n\nThe `organization` scope is the exception and takes no `q`: it reuses the four canonical\ncentral-inbox queries, so its numbers match `getUnread` exactly.\n\nWhich buckets come back varies by scope type and actor. Every scope other than `organization`\nreturns `unread` alone. An `organization` scope returns all four (`unread`, `drafts`,\n`unassigned`, `spam`) for `actor: organization`, and `unread` and `drafts` only for\n`actor: user`. `getUnread` returns all four for both actors; this endpoint omits the two that\nno per-user surface renders, since each costs an aggregation.\n\nGated on the `message-unread-counts` flag, evaluated once per request against the calling\norg. With the flag off the response is `{ \"enabled\": false, \"counts\": {} }` and no\nElasticsearch query is issued.\n", + "tags": [ + "Messages" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnreadCountsPayload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnreadCountsResult" + } + } + } + }, + "400": { + "description": "The request names more scopes than the cap allows, repeats a scope name, or omits both\n`q` and `view` on a scope type that needs a predicate. Over-cap requests are refused rather than truncated:\na silently dropped scope renders as a missing badge, which is indistinguishable from\nzero unread.\n" + }, + "403": { + "description": "Forbidden" + } + } + } + }, "/v2/message/messages/{id}/unread": { "post": { "operationId": "markUnreadMessageV2", @@ -762,6 +828,41 @@ } } }, + "/v2/message/threads:workload": { + "post": { + "operationId": "getAssigneeWorkload", + "summary": "getAssigneeWorkload", + "description": "Return the open-thread workload for a set of user ids.\n\nFor each requested user id, returns the number of *open* threads assigned\ndirectly to that user \u2014 matching what the user sees in their central-inbox\nopen view: in inbox, not trashed, not done, and excluding notification-only\nand spam threads.\n\nOnly threads assigned directly to a user are counted; threads assigned to a\ngroup the user belongs to are not. Intended for assignment load-balancing\n(e.g. even-distribution automations) that need a consistent, inbox-aligned\nworkload per user.\n", + "tags": [ + "Threads" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssigneeWorkloadParams" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssigneeWorkloadResults" + } + } + } + }, + "403": { + "description": "Forbidden" + } + } + } + }, "/v1/message/threads": { "put": { "operationId": "updateThread", @@ -2242,6 +2343,11 @@ "description": "User ID of user sends the message.", "example": "206801" }, + "updated_by": { + "type": "string", + "description": "User ID of the user who last updated (edited) the message. Set for drafts on each save.", + "example": "206801" + }, "subject": { "type": "string", "description": "Subject", @@ -2466,6 +2572,11 @@ "type": "boolean", "description": "If true then this attachment is sent via link. The link have to be inserted to email body by API caller. In this case, service doesn't process this attachment.\n", "default": false + }, + "save_to_entity": { + "type": "boolean", + "description": "If false, this file relation is kept on the message (e.g. for CID/inline rendering) but is NOT propagated to the entity the email is sent from. Used for inline images such as logos, signatures and social icons. Defaults to true.\n", + "default": true } } }, @@ -2481,6 +2592,11 @@ "description": "Whether the thread is marked as Done immediately after sending the message", "default": false }, + "mark_thread_as_read": { + "type": "boolean", + "description": "Whether to mark the thread as read by the sender's org/user after sending a reply.\nOnly applies when `parent_id` is set. When false, the thread's `org_read_message` and\n`user_read_message` arrays are left unchanged, so the thread remains unread for anyone\nwho had it unread before. Defaults to true for backward compatibility.\n", + "default": true + }, "thread": { "type": "object", "required": [ @@ -2563,131 +2679,498 @@ "type": "string", "description": "Template ID used for sending message.", "example": "3f34ce73-089c-4d45-a5ee-c161234e41c3" + }, + "source": { + "type": "string", + "description": "Identifies who triggered the send so server-side policy can branch on it.\nFor `manual` sends, the caller's curated `_tags` are trusted as-is and no\nserver-side enrichment from the referenced `template_id` (e.g. merging the\ntemplate's `email_tags`) is performed. Any non-manual value (or omitting the\nfield) opts into template-derived enrichment.\n", + "enum": [ + "manual", + "automation", + "workflow", + "partner", + "bulk", + "system" + ], + "example": "manual" } } }, - "SearchParamsV2": { + "ThreadView": { "type": "object", - "required": [ - "q" - ], + "additionalProperties": true, + "description": "A central-inbox view, described structurally so the server compiles the query for it. Both the\nthread list and the unread count for a view are compiled from the same description, so the two\ncannot disagree about what the view means.\n\nEvery field is optional and an omitted field adds no condition, so a view narrows the whole\nmailbox rather than being a template with required holes. Unknown fields are ignored.\n", "properties": { - "inbox_id": { - "oneOf": [ - { - "type": "string", - "description": "Inbox ID", - "example": "3f34ce73-089c-4d45-a5ee-c161234e41c3" - }, - { - "type": "array", - "items": { - "type": "string" - }, - "description": "Inbox IDs", - "example": [ - "3f34ce73-089c-4d45-a5ee-c161234e41c3", - "3f34ce73-089c-4d45-a5ee-c161234e41c4" - ] - } - ] + "folder": { + "type": "string", + "enum": [ + "inbox", + "favorite", + "sent", + "trash", + "spam", + "unassignable", + "draft" + ], + "description": "Which sidebar folder's membership predicate to apply." }, - "q": { - "description": "Lucene query syntax supported with ElasticSearch", + "mailbox": { "type": "string", - "example": "subject:\"Request for solar panel price\" AND _tags:INBOX" + "enum": [ + "organization", + "agent" + ], + "description": "Whose mailbox this is. `agent` scopes to threads assigned to the caller or their groups;\n`organization` scopes to the org and is the only mailbox that carries address filtering.\n" }, - "fields": { - "$ref": "#/components/schemas/FieldsParam" + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Saved-filter labels, ANDed. Matched exactly against the tag rather than against its\ntokens, so a label whose words overlap a folder tag no longer lands in that folder.\n" + }, + "purposes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Purpose ids on linked entities, ANDed." + }, + "filters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "unread", + "resolved", + "trash" + ] + }, + "description": "State filters, independent of the folder. `resolved` and `trash` reach the Inbox folder\nonly and are mutually exclusive there; `unread` applies anywhere.\n" }, "from": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Sender addresses to filter on." + }, + "to": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Recipient addresses to filter on." + }, + "assigned_to": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Assignee user ids." + }, + "include_unassigned": { + "type": "boolean", + "description": "Whether threads with no assignee join the `assigned_to` set. A separate flag rather than a\nsentinel entry in that list, so `assigned_to` holds user ids and nothing else.\n" + }, + "date_from_days_ago": { "type": "integer", "minimum": 0, - "default": 0 + "description": "Lower bound of the date range, in days before now. Omitted means the epoch." }, - "size": { + "date_to_days_ago": { "type": "integer", - "minimum": 1, - "default": 10 + "minimum": 0, + "description": "Upper bound of the date range, in days before now. Omitted means now." }, - "hydrate": { - "type": "boolean", - "default": false + "email_filter": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Addresses the user selected in the address filter. Absent and empty differ, and the\ndifference is a real UI state: absent is \"not filtering by address\", empty is \"every\naddress deselected\", which matches nothing.\n\nDistinct from the permission restriction, which the server derives and a caller cannot\nauthor.\n" }, - "include_scores": { - "type": "boolean", - "default": false + "text": { + "type": "string", + "description": "The user's search string, as typed. Expanded across the searched fields server-side and\nnever interpreted as query syntax, so a typed operator or a stray bracket cannot\nre-associate the predicate around it.\n" }, - "sort": { - "type": "string" + "thread_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict the view to these threads. Exists because \"would this thread appear in the view\nthe user is looking at?\" is a real question the inbox asks when a new thread arrives, and\nit is a membership test against the view rather than a different view.\n" }, - "highlight": {} + "pinned_by": { + "type": "string", + "description": "Restrict the view to threads this user pinned. The pinned strip above the list is the same\nview with this one extra condition.\n" + } } }, - "SearchParams": { + "UnreadCountScope": { "type": "object", "required": [ - "q" + "name", + "type" ], "properties": { - "q": { - "description": "Lucene query syntax supported with ElasticSearch", + "name": { "type": "string", - "example": "subject:\"Request for solar panel price\" AND _tags:INBOX" - }, - "from": { - "type": "integer", - "minimum": 0, - "default": 0 + "description": "Caller-chosen key for this scope. Echoed back as the key in `counts`, so it is how the\ncaller matches a number to the sidebar row it belongs to. Must be unique within the\nrequest; duplicates are refused rather than silently collapsed.\n", + "example": "inbox-support" }, - "size": { - "type": "integer", - "minimum": 1, - "default": 10 + "type": { + "type": "string", + "enum": [ + "organization", + "shared_inbox", + "saved_view" + ], + "description": "Decides which buckets come back, and whether `q` is required. `organization` returns all\nfour buckets from the canonical central-inbox queries and takes no `q`. `shared_inbox`\nand `saved_view` return `unread` alone. A `saved_view` scope names its view with `view_id`;\na `shared_inbox` scope needs only its `inbox_id`, since the query follows from the type.\n\nA `shared_inbox` scope additionally requires `actor: organization` and is refused with a\n400 otherwise. A shared inbox is an organization-level construct \u2014 selecting one always\nswitches the mailbox to the organization \u2014 so it has no per-user read state and the\ncombination would compute a number no surface renders. `saved_view` accepts either actor,\nbecause a view's own configuration names its mailbox.\n" }, - "hydrate": { - "type": "boolean", - "default": false - } - } - }, - "SearchIDParams": { - "type": "object", - "properties": { "q": { - "description": "Lucene query syntax supported with ElasticSearch", "type": "string", - "example": "subject:\"Request for solar panel price\" AND _tags:INBOX" + "description": "The scope's query, in Lucene syntax, as passed to `threads:search` for the same scope. The\nserver adds the read-state condition and nothing else, so the count matches that list.\n\nAccepted for `shared_inbox` and `saved_view`; rejected for `organization`.\n\nSuperseded by server-side compilation. It remains accepted for callers whose counts are\nenabled while compilation is not, and is ignored when compilation is enabled. It will be\nremoved once compilation is enabled everywhere counts are.\n", + "example": "_tags.keyword:inbox AND !_tags.keyword:trash" + }, + "view_id": { + "type": "string", + "description": "The id of the saved view this scope counts. The server reads that view and compiles the same\nquery the thread list runs for it, so the count and the list cannot describe the view\ndifferently.\n\nRequired for `saved_view` scopes unless `q` is supplied instead; rejected for the other two\ntypes. A `shared_inbox` scope needs no predicate field at all, because its query follows from\nthe type and its `inbox_id`. An `organization` scope uses the canonical folder queries.\n\nThe view's own shared-inbox filter is read from the stored view, so `inbox_id` need not be\nsent alongside this.\n\nCompilation is enabled per organization by the `message-unread-unified-predicate` feature\nflag. While it is off, a scope supplying only a `view_id` has no query to run and its name is\nreturned in `omitted` rather than counted. A named view that this organization does not have,\nor whose stored configuration cannot be read, is omitted the same way.\n", + "example": "3f34ce73-089c-4d45-a5ee-c161234e41c3" + }, + "inbox_id": { + "description": "Shared inbox ids, resolved to bucket ids the same way `threads:search` resolves them.", + "oneOf": [ + { + "type": "string", + "example": "3f34ce73-089c-4d45-a5ee-c161234e41c3" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] } } }, - "ReadMessagePayload": { + "UnreadCountsPayload": { "type": "object", "required": [ + "actor", "scopes" ], "properties": { - "scopes": { - "description": "The scopes to be used when marking an item as read or unread. The read status will be synced for all provided scopes.", - "example": [ + "actor": { + "type": "string", + "enum": [ "organization", "user" ], + "description": "Which read state to count against \u2014 the org's or the calling user's. Same meaning as\n`getUnread`'s path parameter, and unrelated to a scope's `type`.\n" + }, + "email_filter": { "type": "array", - "default": [ - "organization", - "user" - ], "items": { - "$ref": "#/components/schemas/ReadingScope" + "type": "string" + }, + "description": "Restrict every scope to messages involving these addresses." + }, + "user_groups": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The caller's group ids, as `group_`, with the same meaning and constraints as on\n`threads:search`. Read only when a scope carries a `view`. Entries not matching\n`group_` are dropped. Not an authorization input.\n" + }, + "scopes": { + "type": "array", + "minItems": 1, + "maxItems": 25, + "items": { + "$ref": "#/components/schemas/UnreadCountScope" } } } }, - "ReadingScope": { - "description": "Who is marking an item as read or unread.", - "type": "string", - "enum": [ + "UnreadCountsResult": { + "type": "object", + "required": [ + "enabled", + "counts" + ], + "properties": { + "enabled": { + "type": "boolean", + "description": "False when the `message-unread-counts` flag is off for the calling org, in which case\n`counts` is empty and no Elasticsearch work was done. Callers render no badges rather\nthan rendering zeroes.\n", + "example": true + }, + "counts": { + "type": "object", + "description": "One entry per scope, keyed by the scope's `name`. A scope whose predicate could not be\nbuilt is **absent** rather than zero, because a zero badge is a claim about the mailbox\nand an absent one is a claim about the request.\n", + "additionalProperties": { + "$ref": "#/components/schemas/UnreadCountBuckets" + } + }, + "omitted": { + "type": "array", + "description": "Names of scopes that were accepted but could not be counted. Each appears here and is\nabsent from `counts`, so an omission is distinguishable from a mis-spelled scope name.\n\nCauses, not distinguishable from this field: a `shared_inbox` scope whose ids matched no\nbucket in the organization; a scope with no query to run because compilation is disabled for\nthe organization and no `q` was supplied; and a `view_id` naming a view this organization\ndoes not have or whose stored configuration cannot be read.\n", + "items": { + "type": "string" + } + } + } + }, + "UnreadCountBuckets": { + "type": "object", + "required": [ + "unread" + ], + "properties": { + "unread": { + "type": "number", + "example": 14 + }, + "drafts": { + "type": "number", + "description": "Organization scope only.", + "example": 12 + }, + "unassigned": { + "type": "number", + "description": "Organization scope only.", + "example": 1 + }, + "spam": { + "type": "number", + "description": "Organization scope only.", + "example": 3 + } + } + }, + "SearchParamsV2": { + "type": "object", + "properties": { + "inbox_id": { + "oneOf": [ + { + "type": "string", + "description": "Inbox ID", + "example": "3f34ce73-089c-4d45-a5ee-c161234e41c3" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "description": "Inbox IDs", + "example": [ + "3f34ce73-089c-4d45-a5ee-c161234e41c3", + "3f34ce73-089c-4d45-a5ee-c161234e41c4" + ] + } + ] + }, + "q": { + "description": "Lucene query syntax supported with ElasticSearch.\n\nSend this or `view`, not both. At least one is required; a request with neither is refused\nwith a 400. An empty string is accepted and returns no hits.\n", + "type": "string", + "example": "subject:\"Request for solar panel price\" AND _tags:INBOX" + }, + "view": { + "allOf": [ + { + "$ref": "#/components/schemas/ThreadView" + } + ], + "description": "A view for the server to compile into the query, instead of supplying `q`. When a view is\npresent and compilation is enabled for the calling organization, the compiled query runs and\n`q` is not consulted.\n\nRead by `threads:search` and `threads:searchIds` only. This schema is shared with\n`messages:search`, which compiles no view and ignores the field, so a request there must\nsupply `q`.\n\nCompilation is enabled per organization by the `message-unread-unified-predicate` feature\nflag. While it is off, `q` runs and a request supplying only a view returns no hits.\n" + }, + "user_groups": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The caller's group ids, as `group_`. Read only when a `view` is compiled, where they\ndetermine the agent mailbox's assignee condition and which shared inboxes, and therefore\nwhich addresses, are reachable. Required for those conditions to be correct, because group\nmembership is not present on the id token this service parses.\n\nEntries not matching `group_` are dropped.\n\nNot an authorization input, and not treated as one: naming groups the caller is not in\nwidens what the response includes, exactly as supplying a broader `q` does. Access control\nis enforced elsewhere.\n" + }, + "fields": { + "$ref": "#/components/schemas/FieldsParam" + }, + "from": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "size": { + "type": "integer", + "minimum": 1, + "default": 10 + }, + "hydrate": { + "type": "boolean", + "default": false + }, + "include_scores": { + "type": "boolean", + "default": false + }, + "sort": { + "type": "string" + }, + "highlight": {} + } + }, + "SearchParams": { + "type": "object", + "required": [ + "q" + ], + "properties": { + "q": { + "description": "Lucene query syntax supported with ElasticSearch", + "type": "string", + "example": "subject:\"Request for solar panel price\" AND _tags:INBOX" + }, + "from": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "size": { + "type": "integer", + "minimum": 1, + "default": 10 + }, + "hydrate": { + "type": "boolean", + "default": false + } + } + }, + "SearchIDParams": { + "type": "object", + "properties": { + "view": { + "allOf": [ + { + "$ref": "#/components/schemas/ThreadView" + } + ], + "description": "The view to compile, with the same meaning as on `threads:search`. Present here because\nthis endpoint returns the ordered id set *for that list*: if one compiled its view and the\nother ran an authored `q`, the two would disagree inside a single feature, which is the\ndrift this replaces.\n" + }, + "user_groups": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The caller's group ids, with the same meaning and caveats as on `threads:search`." + }, + "inbox_id": { + "oneOf": [ + { + "type": "string", + "description": "Inbox ID", + "example": "3f34ce73-089c-4d45-a5ee-c161234e41c3" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "description": "Inbox IDs", + "example": [ + "3f34ce73-089c-4d45-a5ee-c161234e41c3", + "3f34ce73-089c-4d45-a5ee-c161234e41c4" + ] + } + ] + }, + "q": { + "description": "Lucene query syntax supported with ElasticSearch", + "type": "string", + "example": "subject:\"Request for solar panel price\" AND _tags:INBOX" + } + } + }, + "AssigneeWorkloadParams": { + "type": "object", + "required": [ + "user_ids" + ], + "properties": { + "user_ids": { + "type": "array", + "description": "User ids to compute the open-thread workload for. At most 100 per request; larger requests are rejected with 422.", + "minItems": 1, + "items": { + "type": "string" + }, + "example": [ + "11000902", + "11001053" + ] + } + } + }, + "AssigneeWorkload": { + "type": "object", + "required": [ + "user_id", + "open_threads" + ], + "properties": { + "user_id": { + "type": "string", + "description": "The user id.", + "example": "11000902" + }, + "open_threads": { + "type": "number", + "description": "Number of open threads assigned directly to the user.", + "example": 2 + } + } + }, + "AssigneeWorkloadResults": { + "type": "object", + "required": [ + "workloads" + ], + "properties": { + "workloads": { + "type": "array", + "description": "Open-thread workload per requested user id, in request order.", + "items": { + "$ref": "#/components/schemas/AssigneeWorkload" + } + } + } + }, + "ReadMessagePayload": { + "type": "object", + "required": [ + "scopes" + ], + "properties": { + "scopes": { + "description": "The scopes to be used when marking an item as read or unread. The read status will be synced for all provided scopes.", + "example": [ + "organization", + "user" + ], + "type": "array", + "default": [ + "organization", + "user" + ], + "items": { + "$ref": "#/components/schemas/ReadingScope" + } + } + } + }, + "ReadingScope": { + "description": "Who is marking an item as read or unread.", + "type": "string", + "enum": [ "organization", "user" ] @@ -2758,6 +3241,254 @@ } } }, + "WorkflowStartedEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "WORKFLOW_STARTED" + ] + }, + "workflow_id": { + "type": "string", + "description": "ID of the workflow/flow execution that was started" + }, + "workflow_name": { + "type": "string", + "description": "Name of the workflow that was started" + } + } + }, + "ThreadUserAssignedEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "THREAD_USER_ASSIGNED" + ] + }, + "added": { + "type": "array", + "description": "User IDs assigned to the thread", + "items": { + "type": "string" + } + }, + "removed": { + "type": "array", + "description": "User IDs unassigned from the thread", + "items": { + "type": "string" + } + } + } + }, + "MessageLabelAddedEvent": { + "type": "object", + "required": [ + "type", + "label" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MESSAGE_LABEL_ADDED" + ] + }, + "label": { + "type": "string", + "description": "The label that was added (raw tag slug, e.g. `sentiments:angry`)" + }, + "label_name": { + "type": "string", + "description": "Resolved taxonomy classification display name (e.g. `Ver\u00e4rgert`), when the label is a classification. Absent for free-form tags." + } + } + }, + "MessageLabelRemovedEvent": { + "type": "object", + "required": [ + "type", + "label" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MESSAGE_LABEL_REMOVED" + ] + }, + "label": { + "type": "string", + "description": "The label that was removed (raw tag slug, e.g. `sentiments:angry`)" + }, + "label_name": { + "type": "string", + "description": "Resolved taxonomy classification display name (e.g. `Ver\u00e4rgert`), when the label is a classification. Absent for free-form tags." + } + } + }, + "MessageEntityLinkedEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MESSAGE_ENTITY_LINKED" + ] + }, + "entities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelineLinkedEntity" + } + }, + "link_kind": { + "type": "string", + "enum": [ + "manual", + "auto" + ] + } + } + }, + "MessageEntityUnlinkedEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MESSAGE_ENTITY_UNLINKED" + ] + }, + "entities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelineLinkedEntity" + } + }, + "link_kind": { + "type": "string", + "enum": [ + "manual", + "auto" + ] + } + } + }, + "MessageAutoReplySentEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "MESSAGE_AUTO_REPLY_SENT" + ] + }, + "reply_message_id": { + "type": "string", + "description": "ID of the message that was sent as the automatic reply" + }, + "parent_message_id": { + "type": "string", + "description": "ID of the message the automatic reply responded to" + } + } + }, + "ThreadMovedToInboxEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "THREAD_MOVED_TO_INBOX" + ] + }, + "target_inbox_id": { + "type": "string", + "description": "ID of the shared inbox the thread was moved to" + }, + "target_inbox_name": { + "type": "string", + "description": "Name of the shared inbox the thread was moved to" + } + } + }, + "ThreadTrashedEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "THREAD_TRASHED" + ] + } + } + }, + "ThreadRestoredEvent": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "THREAD_RESTORED" + ] + } + } + }, + "TimelineLinkedEntity": { + "type": "object", + "required": [ + "entity_id" + ], + "properties": { + "entity_id": { + "type": "string" + }, + "schema": { + "type": "string", + "description": "Entity schema slug, e.g. \"opportunity\"" + } + } + }, + "TimelineActor": { + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, "TimelineEventData": { "type": "object", "discriminator": { @@ -2769,6 +3500,36 @@ }, { "$ref": "#/components/schemas/ThreadOpenEvent" + }, + { + "$ref": "#/components/schemas/WorkflowStartedEvent" + }, + { + "$ref": "#/components/schemas/ThreadUserAssignedEvent" + }, + { + "$ref": "#/components/schemas/MessageLabelAddedEvent" + }, + { + "$ref": "#/components/schemas/MessageLabelRemovedEvent" + }, + { + "$ref": "#/components/schemas/MessageEntityLinkedEvent" + }, + { + "$ref": "#/components/schemas/MessageEntityUnlinkedEvent" + }, + { + "$ref": "#/components/schemas/MessageAutoReplySentEvent" + }, + { + "$ref": "#/components/schemas/ThreadMovedToInboxEvent" + }, + { + "$ref": "#/components/schemas/ThreadTrashedEvent" + }, + { + "$ref": "#/components/schemas/ThreadRestoredEvent" } ] }, @@ -2779,13 +3540,47 @@ "data" ], "properties": { + "id": { + "type": "string", + "description": "Activity id (ActivityItem._id), for deep-linking to the item in the activity feed" + }, "data": { "$ref": "#/components/schemas/TimelineEventData" }, "timestamp": { "type": "string", "description": "Timestamp of the event", - "example": "2024-01-01T00:00:00Z" + "example": "2024-01-01T00:00:00.000Z" + }, + "message_id": { + "type": "string", + "description": "For message-level events, the message the activity is anchored to" + }, + "source": { + "type": "string", + "enum": [ + "user", + "automation", + "system" + ] + }, + "automated": { + "type": "boolean", + "description": "Whether the activity was performed automatically (automation/system)" + }, + "actor": { + "$ref": "#/components/schemas/TimelineActor" + }, + "automation": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } } } }, diff --git a/packages/cli/definitions/metering.json b/packages/cli/definitions/metering.json index 11c8bdbbe..c3853fe2e 100644 --- a/packages/cli/definitions/metering.json +++ b/packages/cli/definitions/metering.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "Metering API", - "version": "1.0.0", + "version": "1.2.0", "description": "The Metering API manages smart meter data, meter counters, and meter readings for epilot customers and administrators.\n\nIt supports two audiences:\n- **ECP (End Customer Portal)**: Portal users can view their meters, counters, and submit readings via the customer portal.\n- **ECP Admin**: Internal epilot users and ERP integrations can create, update, and bulk-manage meter readings.\n\nKey capabilities:\n- Retrieve meters and counters associated with a customer or contract\n- Submit individual or bulk meter readings (with optional validation skip)\n- Batch upsert/delete readings using the v2 endpoint\n- Query historical readings by date interval with cumulative or relative consumption modes\n- Retrieve allowed reading ranges to guide end customers entering readings\n" }, "tags": [ @@ -554,6 +554,9 @@ }, { "$ref": "#/components/parameters/DirectQueryParam" + }, + { + "$ref": "#/components/parameters/CreateTicketQueryParam" } ], "requestBody": { @@ -722,6 +725,9 @@ }, { "$ref": "#/components/parameters/DirectQueryParam" + }, + { + "$ref": "#/components/parameters/CreateTicketQueryParam" } ], "requestBody": { @@ -785,6 +791,101 @@ } } }, + "/v2/metering/readings/prune": { + "post": { + "operationId": "pruneMeterReadings", + "summary": "pruneMeterReadings", + "description": "Deletes every reading of a meter whose `external_id` is NOT in the provided keep list — in a single request.\n\nThe prune scope can optionally be narrowed to a single counter (`counter_id`) and/or a reading `source` (e.g. `ERP`).\nReplaces the client-side pattern of paginating the full reading history and issuing chunked batch deletes.\n\nReadings without an `external_id`:\n- when a `source` filter is provided, they are **deleted** — they cannot be referenced by any keep list\n- when no `source` filter is provided, they are **kept** (conservative default)\n\nDeletions reuse the same internal path as `batchWriteMeterReadings` with `operation: delete`: the same\nper-reading lifecycle events are emitted, and providing `activity_id` suppresses the per-reading delete\nactivities and attaches the given activity to the affected meter and counters instead. Delete operations\nnever create manual-intervention tickets; `create_ticket` is accepted for call-site parity with\n`batchWriteMeterReadings`.\n\n`keep_external_ids` is limited to 10000 entries.\n", + "tags": [ + "ECP Admin" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "async", + "description": "Don't wait for the deletions to become visible in GetReadings API. Useful for large prunes", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "$ref": "#/components/parameters/ActivityIdQueryParam" + }, + { + "$ref": "#/components/parameters/CreateTicketQueryParam" + }, + { + "in": "query", + "name": "dry_run", + "description": "When true, computes and returns the deleted/kept counts without deleting anything.", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "description": "Prune scope and keep list.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PruneMeterReadingsPayload" + } + } + } + }, + "responses": { + "200": { + "description": "Meter readings pruned successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "deleted_count": { + "type": "integer", + "description": "Number of readings deleted (or that would be deleted when `dry_run=true`).", + "example": 42 + }, + "kept_count": { + "type": "integer", + "description": "Number of readings in scope that were kept.", + "example": 12 + } + } + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/InvalidRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, "/v1/metering/reading/submission": { "post": { "operationId": "createMeterReadingFromSubmission", @@ -2347,6 +2448,38 @@ "propertyName": "operation" } }, + "PruneMeterReadingsPayload": { + "type": "object", + "required": [ + "meter_id", + "keep_external_ids" + ], + "properties": { + "meter_id": { + "$ref": "#/components/schemas/EntityId", + "description": "The ID of the meter whose readings are pruned" + }, + "counter_id": { + "$ref": "#/components/schemas/EntityId", + "description": "Optionally narrows the prune scope to a single counter of the meter" + }, + "source": { + "$ref": "#/components/schemas/Source", + "description": "Optionally only prune readings with this source (e.g. `ERP`)" + }, + "keep_external_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Readings whose `external_id` is contained in this list are kept; every other reading in scope is deleted.\nAn empty array deletes all readings in scope (subject to the no-external-id rule described on the endpoint).\nLimited to 10000 entries.\n", + "example": [ + "erp-reading-1", + "erp-reading-2" + ] + } + } + }, "UpdateMeterReading": { "type": "object", "required": [ @@ -2969,6 +3102,16 @@ "$ref": "#/components/schemas/ActivityId" } }, + "CreateTicketQueryParam": { + "name": "create_ticket", + "in": "query", + "required": false, + "description": "Controls whether a manual-intervention (\"Übermittlung Zählerstand\") ticket is created for the\nwritten readings. Defaults to true, preserving portal/journey behaviour (readings with\nsource ECP or journey-submission mint a review ticket).\n\nSet to false for authoritative, system-driven writes — e.g. the ERP inbound integration\npipeline — whose readings arrive already confirmed and must not spawn a review ticket. This\nalso prevents the ticket-triggered `ServiceMeterReadingAdded` echo back to the ERP.\n", + "schema": { + "type": "boolean", + "default": true + } + }, "SkipValidationQueryParam": { "name": "skip_validation", "in": "query", @@ -2981,5 +3124,9 @@ } } }, - "servers": [] + "servers": [ + { + "url": "https://metering.sls.epilot.io" + } + ] } diff --git a/packages/cli/definitions/notification.json b/packages/cli/definitions/notification.json index 7180fd924..84e4fb334 100644 --- a/packages/cli/definitions/notification.json +++ b/packages/cli/definitions/notification.json @@ -1013,6 +1013,20 @@ "in_app": false } } + }, + "allowed_channels": { + "description": "Optional delivery-channel ceiling. When present, delivery is restricted to the intersection of this set and each recipient's own notification preferences — it can only NARROW delivery, never force a channel on. Absent means no restriction (recipient preferences and per-type defaults apply as before). An empty array suppresses all channels.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "email", + "in_app" + ] + }, + "example": [ + "in_app" + ] } }, "required": [ diff --git a/packages/cli/definitions/permissions.json b/packages/cli/definitions/permissions.json index 7964337eb..7c42f50a2 100644 --- a/packages/cli/definitions/permissions.json +++ b/packages/cli/definitions/permissions.json @@ -928,15 +928,21 @@ { "$ref": "#/components/schemas/EqualsCondition" }, + { + "$ref": "#/components/schemas/NotEqualsCondition" + }, { "$ref": "#/components/schemas/EqualsCurrentUserCondition" + }, + { + "$ref": "#/components/schemas/NotEqualsCurrentUserCondition" } ] } ] }, "EqualsCurrentUserCondition": { - "description": "Check if any relation_user attribute on the entity contains the current user. When attribute is provided, only that specific attribute path is checked.", + "description": "Check if any relation_user attribute on the entity contains the current user. When attribute is provided, only that specific attribute path is checked. When attributes is provided, it takes precedence over attribute and the condition passes when the current user appears in ANY of the listed attribute paths.", "type": "object", "properties": { "attribute": { @@ -944,6 +950,18 @@ "description": "Optional JSON path to a specific user attribute. When omitted, all relation_user attributes on the entity are scanned.", "example": "assignee.*.user_id" }, + "attributes": { + "type": "array", + "description": "Optional list of JSON paths to user attributes. Takes precedence over attribute.", + "minItems": 1, + "items": { + "type": "string" + }, + "example": [ + "assignee.*.user_id", + "owner.*.user_id" + ] + }, "operation": { "type": "string", "enum": [ @@ -955,6 +973,38 @@ "operation" ] }, + "NotEqualsCurrentUserCondition": { + "description": "Check if the current user is absent from the relation_user attributes on the entity. When attribute is provided, only that specific attribute path is checked. When attributes is provided, it takes precedence over attribute and the condition passes only when the current user appears in NONE of the listed attribute paths.", + "type": "object", + "properties": { + "attribute": { + "type": "string", + "description": "Optional JSON path to a specific user attribute. When omitted, all relation_user attributes on the entity are scanned.", + "example": "assignee.*.user_id" + }, + "attributes": { + "type": "array", + "description": "Optional list of JSON paths to user attributes. Takes precedence over attribute.", + "minItems": 1, + "items": { + "type": "string" + }, + "example": [ + "assignee.*.user_id", + "owner.*.user_id" + ] + }, + "operation": { + "type": "string", + "enum": [ + "not_equals_current_user" + ] + } + }, + "required": [ + "operation" + ] + }, "EqualsCondition": { "description": "Check if attribute equals to any of the values", "type": "object", @@ -971,6 +1021,35 @@ }, "values": { "type": "array", + "minItems": 1, + "items": { + "example": "Qualification" + } + } + }, + "required": [ + "attribute", + "operation", + "values" + ] + }, + "NotEqualsCondition": { + "description": "Passes when the attribute does not equal any of the values. Records where the attribute is missing or empty pass the condition.", + "type": "object", + "properties": { + "attribute": { + "type": "string", + "example": "workflows.primary.task_name" + }, + "operation": { + "type": "string", + "enum": [ + "not_equals" + ] + }, + "values": { + "type": "array", + "minItems": 1, "items": { "example": "Qualification" } @@ -1511,5 +1590,9 @@ } } }, - "servers": [] + "servers": [ + { + "url": "https://permissions.sls.epilot.io" + } + ] } diff --git a/packages/cli/definitions/pricing.json b/packages/cli/definitions/pricing.json index b483273c5..9e111dc83 100644 --- a/packages/cli/definitions/pricing.json +++ b/packages/cli/definitions/pricing.json @@ -2,8 +2,8 @@ "openapi": "3.0.3", "info": { "title": "Pricing API", - "description": "The `pricing-api` hub sets the foundations for the following Pricing APIs:\n\n### Order API\nThis api enables the management of orders in epilot 360, providing features such as:\n - Automatic calculation of totals and price breakdowns for taxes on the Order entity\n - Product and pricing data validation\n\n### Shopping Cart API\nUsed to interact with a cart during a customer's checkout session, providing:\n - An unified data model to model a Shopping Cart\n - Product and pricing data validation\n - Checkout a cart into an order or quote\n\n### Catalog API\nProvides a way to query the entire catalog of products and prices.\n\n### Availability API\nProvides endpoints for querying products availability by a set of predefined dimensions.\n\n### Spot Market API\nProvides endpoints to fetch (historic) spot market price data.\n\n### External Integrations API\nProvides endpoints for external integrations. E.g. GetAG.\n\n### External Catalog API\nProvides endpoints for external catalog.\n\n### Product Recommendations API\nProvides endpoints for product recommendations.\n", - "version": "1.1.0", + "description": "The `pricing-api` hub sets the foundations for the following Pricing APIs:\n\n### Order API\nThis api enables the management of orders in epilot 360, providing features such as:\n - Automatic calculation of totals and price breakdowns for taxes on the Order entity\n - Product and pricing data validation\n\n### Shopping Cart API\nUsed to interact with a cart during a customer's checkout session, providing:\n - An unified data model to model a Shopping Cart\n - Product and pricing data validation\n - Checkout a cart into an order or quote\n\n### Catalog API\nProvides a way to query the entire catalog of products and prices.\n\n### Availability API\nProvides endpoints for querying products availability by a set of predefined dimensions.\n\n### Spot Market API\nProvides endpoints to fetch (historic) spot market price data.\n\n### External Integrations API\nProvides endpoints for external integrations. E.g. GetAG.\n\n### External Catalog API\nProvides endpoints for external catalog.\n\n### Product Recommendations API\nProvides endpoints for product recommendations.\n\n### Conditional Pricing API\nProvides endpoints for resolving conditional Products, Prices and Coupons, and for authoring the conditions they vary by.\n", + "version": "1.2.1", "termsOfService": "https://epilot.cloud/agb", "contact": { "name": "Support", @@ -48,6 +48,10 @@ "name": "Product Recommendations API", "description": "Provides endpoints for product recommendations.\n" }, + { + "name": "Conditional Pricing API", + "description": "Provides endpoints for resolving conditional Products, Prices and Coupons — the variant of an\nentity that applies to a given context — and for authoring the conditions they vary by.\n" + }, { "name": "order_schema", "x-displayName": "Order", @@ -96,7 +100,8 @@ "External Integrations API", "External Catalog API", "Product Recommendations API", - "Promo Codes API" + "Promo Codes API", + "Conditional Pricing API" ] }, { @@ -136,7 +141,7 @@ "/v1/pricing:compute": { "post": { "description": "Computes a set of pricing details that can be persisted on an entity with the pricing capability enabled, e.g: Orders or Contracts.", - "summary": "calculatePricingDetails", + "summary": "$calculatePricingDetails", "operationId": "$calculatePricingDetails", "tags": [ "Order API" @@ -876,7 +881,7 @@ "name": "id", "description": "Order entity ID", "schema": { - "type": "string" + "$ref": "#/components/schemas/EntityId" }, "required": true, "example": "9d4602d3-03be-4d85-86b2-f3c6555fc606" @@ -995,7 +1000,7 @@ "post": { "description": "Checkouts a cart and executes the specified checkout `mode` process.\n\nA Checkout implicitly finalizes the provided cart (if not transient from a fast-checkout) and behaves in one of the following modes:\n- `create_order` (**default**): the payment happens at a later date or managed by 3rd-party CRM (SAP)\n- `create_invoice`: the payment happens on the online checkout (paypal, stripe, adyen)\n- `create_quote`: the checkout represents a price quote request\n\nFast checkout is also supported, by passing the Cart contents directly.\nWhen a fast checkout is performed the cart is considered transient and there is no cart persistance.\n\nIf the checkout `mode` is omitted, the `mode` will default to `create_order`.\n", "operationId": "$checkoutCart", - "summary": "checkoutCart", + "summary": "$checkoutCart", "security": [ {}, { @@ -1064,7 +1069,7 @@ "post": { "description": "Provides a querying functionalities over products and prices of the Catalog for a given organization.", "operationId": "$searchCatalog", - "summary": "searchCatalog", + "summary": "$searchCatalog", "security": [ {}, { @@ -1131,7 +1136,7 @@ "post": { "description": "Provides a querying functionalities over products and prices of the Catalog for a given organization.", "operationId": "$privateSearchCatalog", - "summary": "privateSearchCatalog", + "summary": "$privateSearchCatalog", "tags": [ "Catalog API" ], @@ -1172,7 +1177,7 @@ "/v1/public/validate-promo-codes": { "post": { "description": "Validate a list of promo codes against a list of coupons", - "summary": "validatePromoCodes", + "summary": "$validatePromoCodes", "operationId": "$validatePromoCodes", "tags": [ "Promo Codes API" @@ -1288,7 +1293,7 @@ "post": { "description": "The availability check endpoint", "operationId": "$availabilityCheck", - "summary": "availabilityCheck", + "summary": "$availabilityCheck", "security": [ { "EpilotPublicAuth": [] @@ -1402,7 +1407,7 @@ "get": { "description": "Validates an availability file, it returns an array of errors if the file is invalid", "operationId": "$validateAvailabilityFile", - "summary": "validateAvailabilityFile", + "summary": "$validateAvailabilityFile", "tags": [ "Availability API" ], @@ -1483,7 +1488,7 @@ "get": { "description": "Get a series of historic energy prices for a given time period, market and bidding zone.", "operationId": "$historicMarketPrices", - "summary": "historicMarketPrices", + "summary": "$historicMarketPrices", "tags": [ "Spot Market API" ], @@ -1593,7 +1598,7 @@ "get": { "description": "Get the average energy prices for a given time period, market and bidding zone.", "operationId": "$averageMarketPrice", - "summary": "averageMarketPrice", + "summary": "$averageMarketPrice", "tags": [ "Spot Market API" ], @@ -1712,7 +1717,7 @@ "example": "external-catalog" } ], - "summary": "searchExternalProducts", + "summary": "$searchExternalProducts", "tags": [ "External Integrations API" ], @@ -1874,7 +1879,7 @@ "example": "external-catalog" } ], - "summary": "searchExternalProductRecommendations", + "summary": "$searchExternalProductRecommendations", "tags": [ "External Integrations API" ], @@ -2261,7 +2266,7 @@ "example": "getag" } ], - "summary": "searchProviders", + "summary": "$searchProviders", "tags": [ "External Integrations API" ], @@ -2413,7 +2418,7 @@ "example": "getag" } ], - "summary": "searchStreets", + "summary": "$searchStreets", "tags": [ "External Integrations API" ], @@ -2513,7 +2518,7 @@ "example": "getag" } ], - "summary": "calculatePricingDetails", + "summary": "$computePrice", "tags": [ "External Integrations API" ], @@ -2603,7 +2608,7 @@ "get": { "description": "Gets the credentials for a given integration / organization", "operationId": "$getCredentials", - "summary": "getCredentials", + "summary": "$getCredentials", "tags": [ "External Integrations API" ], @@ -2665,7 +2670,7 @@ "put": { "description": "Saves the credentials for a given integration / organization", "operationId": "$saveCredentials", - "summary": "saveCredentials", + "summary": "$saveCredentials", "tags": [ "External Integrations API" ], @@ -2719,7 +2724,7 @@ "delete": { "description": "Delete the credentials for a given integration / organization", "operationId": "$deleteCredentials", - "summary": "deleteCredentials", + "summary": "$deleteCredentials", "tags": [ "External Integrations API" ], @@ -2762,7 +2767,7 @@ "EpilotPublicAuth": [] } ], - "summary": "getExternalCatalogProducts", + "summary": "$getExternalCatalogProducts", "tags": [ "External Catalog API" ], @@ -2923,7 +2928,7 @@ "EpilotPublicAuth": [] } ], - "summary": "getExternalCatalogProductRecommendations", + "summary": "$getExternalCatalogProductRecommendations", "tags": [ "External Catalog API" ], @@ -3266,169 +3271,1872 @@ "post": { "description": "Get a list of product recommendations based on the search parameters.", "operationId": "$productRecommendations", - "summary": "productRecommendations", + "summary": "$productRecommendations", "security": [ { "EpilotPublicAuth": [] } ], - "tags": [ - "Product Recommendations API" - ], - "parameters": [ - { - "in": "header", - "name": "X-Ivy-Org-ID", - "description": "The target Organization Id represented by the caller", - "example": "739224", - "schema": { - "type": "string" - } + "tags": [ + "Product Recommendations API" + ], + "parameters": [ + { + "in": "header", + "name": "X-Ivy-Org-ID", + "description": "The target Organization Id represented by the caller", + "example": "739224", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductRecommendationSearch" + }, + "examples": { + "Search product recommendations by id": { + "value": { + "product_recommendation_ids": [ + "22c134b7-8dd8-4872-a5c1-8fc6dd4ac2ee" + ] + } + }, + "Search product recommendations by id with location filter": { + "value": { + "product_recommendation_ids": [ + "22c134b7-8dd8-4872-a5c1-8fc6dd4ac2ee" + ], + "filters": { + "location": { + "postal_code": "50670", + "city": "Köln" + } + } + } + }, + "Search product recommendations by source product and price": { + "value": { + "catalog_item": { + "product_id": "aa1ebf85-f36e-46ff-978c-ed948c646f12", + "price_id": "4ecf9a61-a974-4594-bae2-bcb798f54d76" + } + } + }, + "Search product recommendations by contract": { + "value": { + "contract_id": "98ffca40-46ab-4288-af10-a478893b2aa2" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The product recommendations result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductRecommendationResponse" + }, + "examples": { + "Product recommendations": { + "value": { + "hits": 1, + "results": [ + { + "_title": "Tarifwechsel 2025", + "_created_at": "2025-01-29T15:46:41.014Z", + "_updated_at": "2025-01-29T15:46:41.014Z", + "name": "Tarifwechsel 2025", + "type": "change", + "source_product": { + "$relation": [ + { + "entity_id": "e2b12b54-d904-42b7-ac44-802d8ebaadf4" + } + ] + }, + "source_price": { + "$relation": [ + { + "entity_id": "255877f3-f9c2-483e-9c71-89088c142bfc" + } + ] + }, + "_schema": "product_recommendation", + "offers": [ + { + "target_id": "f9aa8757-505b-46bd-9310-605d64849565", + "items": [ + { + "product_id": "70ad8a08-46c2-473b-b1c5-8bccfb85e452", + "price_id": "26c9c39d-db4a-4132-af2e-86ab5c7b156c" + }, + { + "product_id": "dd97a2a4-e48b-43c5-8717-7ac563c67e05", + "price_id": "4ecf9a61-a974-4594-bae2-bcb798f54d76" + }, + { + "product_id": "aa1ebf85-f36e-46ff-978c-ed948c646f12", + "price_id": "4ecf9a61-a974-4594-bae2-bcb798f54d76" + } + ] + } + ], + "_id": "6d872912-ef8a-4815-b9db-01744c0828ac", + "_org": "739224" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/conditional-pricing/{slug}/condition-sets": { + "get": { + "description": "Returns the condition sets built in for one conditional entity type: the situations a\nconditional Product, Price or Coupon is commonly varied by, ready to be copied into that\nschema's `conditions` array and extended or modified from there.\n\nWhich sets exist depends on the schema — an offer window is a Product's dimension, a delivery\narea is a Price's and a Coupon's — so only the sets built in for `slug` are returned.\n\nStatic, read-only reference data. The catalog is the same for every organization and is not\napplied to any schema by this endpoint — adding conditions to a schema stays an Entity API\nwrite.\n", + "operationId": "$getConditionSets", + "summary": "$getConditionSets", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type whose built-in condition sets to return", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + } + ], + "responses": { + "200": { + "description": "The condition sets built in for this entity type", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionSetCatalog" + } + } + } + }, + "400": { + "description": "Invalid request, e.g. the slug names no conditional entity type", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/conditional-pricing:resolve": { + "post": { + "description": "Resolves which of a conditional entity's variants apply to a situation, and returns each one\ncomposed: the base entity overlaid with the values of the version in effect at `as_of`.\n\nResolution is two selections in a fixed order — the variant, by matching `context` against\nthe conditions each variant pins; then the version, by `as_of`. It is always scoped to one\nlogical entity, so it stays a cheap, predictable lookup rather than an open search.\n\nMatching follows two rules worth knowing before assembling a context. A condition a variant\ndoes **not** pin matches any value, which is what lets a condition be added to a schema\nwithout breaking the variants that already exist. A condition **missing from `context`**,\nhowever, does not satisfy one a variant pinned: an incomplete integration resolves to\nnothing rather than silently matching another segment's variants.\n\nWhen nothing matches, the entity's `default` variant is returned if it has one. There is no\nimplicit fallback to the unmodified base entity — its values are the ones no variant\noverrode, which is not an answer to \"what applies here\".\n\nAvailability is a separate mechanism and is never consulted here.\n", + "operationId": "$resolveConditionalEntity", + "summary": "$resolveConditionalEntity", + "tags": [ + "Conditional Pricing API" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolveConditionalEntityRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The variants that apply, each composed with the version in effect. Empty when nothing\napplies and the entity has no `default` variant. With `resolve_one`, exactly one result.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolvedVariants" + } + } + } + }, + "400": { + "description": "The context is not usable against this schema: it names an undefined condition\n(`CONDITION_UNDEFINED`), applies an operator the condition's type does not support\n(`OPERATOR_UNSUPPORTED`), carries a value malformed for its type (`CONTEXT_FORMAT_INVALID`),\nor selects more variants than one response may carry (`TOO_MANY_MATCHES`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "No such schema or entity, or — with `resolve_one` — nothing applied and the entity has no\n`default` variant (`NOT_FOUND`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "Several variants apply while a single result was requested (`AMBIGUOUS_RESOLUTION`); the\ncandidates are in `details`.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + } + }, + "/v1/conditional-pricing/{slug}/entities/{entity_id}/variants": { + "post": { + "description": "Creates one variant of a conditional entity, together with the first version carrying its\nvalues. Never two calls: a variant that existed without a version would be an entity holding\na condition tuple it cannot answer with.\n\nThe body pins the situation the variant applies to. Pins are exact values only — predicates\nare a read-side concept and are rejected here — and are stored canonicalized for their\ncondition's type, so two spellings of one instant, or one town written two ways, are one\nvariant rather than two that no context can tell apart.\n\nThree write rules are worth knowing before the first call:\n\n- A variant must pin at least one condition or be marked `default`. A variant pinning nothing\n would be a universal wildcard matching every resolve, which is a far more dangerous thing\n than a fallback and far easier to create by accident.\n- `default` is a property of the variant, set by the `default` flag, and is never a value in\n `conditions` — not even `false`. A `default` variant cannot pin anything else, and an entity\n can have only one, enforced by the ordinary condition-tuple guard rather than by a rule of\n its own. Any entity may have one; nothing is declared in the schema to allow it.\n- Condition values are immutable afterwards. A variant's identity is the situation it applies\n to, and orders and contracts pin it. **A condition added to a schema that already has\n variants is effectively one-way**: every existing variant is a wildcard on the new\n dimension, but the first variant that pins it is ambiguous against all of them, and\n retro-pinning the others is blocked by this same rule.\n\nAttribute values are applied only for attributes currently carrying `overridable_attribute`.\nMetadata and non-overridable fields present in the body are ignored rather than rejected, so a\nclient working from a slightly stale schema snapshot still succeeds.\n\n`variant_id` is always server-generated and returned, and is not accepted in the body — the\nrequest schema admits no such property. It is the durable key orders and contracts pin, so it\ncannot be something two independent importers could collide on.\n", + "operationId": "$createConditionalVariant", + "summary": "$createConditionalVariant", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity to add a variant to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVariantRequest" + } + } + } + }, + "responses": { + "201": { + "description": "The variant, as created, together with its first version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatedVariant" + } + } + } + }, + "400": { + "description": "The variant cannot be created as described: it pins nothing and is not the default, pins\na condition the schema does not declare (`CONDITION_UNDEFINED`), pins a `select` value\noutside the vocabulary the condition declares (`CONDITION_VALUE_INVALID`), pins the\nfallback marker directly under either of its names (`default` or `_default`), carries a\nvalue malformed for its condition's type, or the entity already holds every variant it\nmay hold.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "No such schema, or no such entity (`NOT_FOUND`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "Another variant of this entity already pins this exact combination of condition values\n(`TUPLE_CONFLICT`, naming it in `details.conflicting_variant_id`) — which is also how a\nsecond `default` variant is refused — or the entity's items are being written\nconcurrently (`WRITE_CONFLICT`, retryable).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + } + }, + "/v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}": { + "get": { + "description": "Returns the version of this variant that is currently in effect — the one with the latest\n`valid_from` at or before now.\n\nThe \"open this variant\" read: no date arithmetic is asked of the caller, and what comes back\ncarries the `_revision` a write to that version has to be sent with, so an editing screen can\nload and save without working out which version it is looking at.\n\nWhat is returned is the version's own attribute overrides, not the base entity overlaid with\nthem. Composing the two is what `:resolve` answers.\n\nA variant staged ahead of its launch has versions but none of them in effect, and is reported\nas having none rather than as not existing — the two are fixed differently.\n", + "operationId": "$getActiveConditionalVariantVersion", + "summary": "$getActiveConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + } + ], + "responses": { + "200": { + "description": "The version, as stored", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VariantVersion" + } + } + } + }, + "400": { + "description": "Invalid request, e.g. the slug names no conditional entity type.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "No such variant under this schema, or it has no version at the instant addressed\n(`NOT_FOUND`). A variant whose versions are all still scheduled has none in effect, which is\nreported as such rather than as a missing variant.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + }, + "put": { + "description": "Replaces the values of the version currently in effect, wholesale.\n\nThe body is the complete set of attribute overrides: an attribute the variant may override and\nthat is absent from it stops being overridden. Attributes the variant may **not** override are\nignored where the body carries them, and their stored value is kept rather than dropped — a\nroutine full-snapshot write must not erase an override the moment its attribute's flag happens\nto be off.\n\nEditing the version in effect is the ordinary way a live price is corrected, and warns about\nnothing: what changes is what that version *says*, not which version is in effect.\n\nNeither `valid_from` nor `conditions` can be changed here. Both are accepted when they match\nwhat is stored, so a client building its body from the version it loaded need not strip them\nout first, and both are refused when they name something else.\n", + "operationId": "$replaceActiveConditionalVariantVersion", + "summary": "$replaceActiveConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceVersionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The version, as the write left it, together with anything the write moved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WrittenVariantVersion" + } + } + } + }, + "400": { + "description": "The write cannot be applied as described: it would move the version it addresses to another\n`valid_from`, or change the conditions its variant is pinned to — both identity rather than\ncontent, and both fixed at creation. Also when `_revision` is missing or is not a revision\nmarker.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "No such variant under this schema, or it has no version at the instant addressed\n(`NOT_FOUND`). A variant whose versions are all still scheduled has none in effect, which is\nreported as such rather than as a missing variant.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "The version has been written since `_revision` was read (`WRITE_CONFLICT`, retryable after\nre-reading the version).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + }, + "patch": { + "description": "Changes only the fields it names on the version currently in effect.\n\nEverything the body does not mention is left as stored — the \"just nudge this number\" write. A\n`null` is a value like any other rather than a deletion; a client that wants an attribute to\nstop being overridden sends the complete snapshot without it through `PUT`.\n\nAttempting to change a pinned condition value is refused here in particular: a partial update\nis the path a caller reaches for by accident, and a variant's conditions are the situation it\napplies to, which the orders and contracts pinning it depend on not shifting.\n", + "operationId": "$patchActiveConditionalVariantVersion", + "summary": "$patchActiveConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchVersionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The version, as the write left it, together with anything the write moved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WrittenVariantVersion" + } + } + } + }, + "400": { + "description": "The write cannot be applied as described: it would move the version it addresses to another\n`valid_from`, or change the conditions its variant is pinned to — both identity rather than\ncontent, and both fixed at creation. Also when `_revision` is missing or is not a revision\nmarker.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "No such variant under this schema, or it has no version at the instant addressed\n(`NOT_FOUND`). A variant whose versions are all still scheduled has none in effect, which is\nreported as such rather than as a missing variant.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "The version has been written since `_revision` was read (`WRITE_CONFLICT`, retryable after\nre-reading the version).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + }, + "delete": { + "description": "Removes one variant of a conditional entity: the condition tuple it holds, its registration\nin the search index, and every version it accumulated.\n\nTwo phases. The first frees the tuple and deregisters the variant, and is what makes the\ncombination of condition values immediately reusable — the second removes the version rows in\nbatches afterwards. A response arrives only once both have finished for this request, but the\ntuple is reusable from the moment the first completes, whether or not the second did: a\nvariant with more versions than one transaction can carry is the ordinary case, not an edge\none. An interrupted delete is safe to send again; it picks up where it stopped.\n\nNothing is archived. A variant an order or contract pins stops resolving, and hydration drops\nthe reference leniently rather than failing the read.\n\nThis removes the **variant**, not one of its versions. To remove a single version, name it on\n`…/variants/{variant_id}/versions/{valid_from}` — including the one currently in effect, which\ndeliberately has no \"delete whichever is live\" shorthand: that is exactly the write nobody\nshould be able to ask for without saying which version they meant.\n", + "operationId": "$deleteConditionalVariant", + "summary": "$deleteConditionalVariant", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant to remove", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + } + ], + "responses": { + "200": { + "description": "What the delete removed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedVariant" + } + } + } + }, + "400": { + "description": "Invalid request, e.g. the slug names no conditional entity type", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "This entity has no such variant (`NOT_FOUND`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "The variant's items are being written concurrently (`WRITE_CONFLICT`, retryable).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + } + }, + "/v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}/versions": { + "post": { + "description": "Appends a version to a variant: a new set of values taking effect at its own instant.\n\nThis is how a price changes. No version carries an end date and nothing is superseded\nexplicitly — the version in effect at an instant is simply the one with the latest `valid_from`\nat or before it, so appending a later version is the whole of \"this is the new price from then\non\". A version dated in the future is staged and excluded from resolution until its date.\n\n**A version is never refused for being late.** A `valid_from` in the past is written like any\nother and answered with warnings in `warnings` naming what it moved — what resolves now, what a\npast-dated read returns, or both. Correcting a price that took effect last week is ordinary\nwork; the alternative, deleting and recreating the variant, breaks every order and contract\npinning its id.\n\nWhat is refused is appending at a `valid_from` the variant already has: that write means either\n\"replace it\" or \"and also this\", and only the caller knows which. The two operations both\nexist, on the dated version path.\n\nThe variant's `conditions` are its identity and are fixed at creation; they may be sent back\nunchanged but never changed.\n", + "operationId": "$appendConditionalVariantVersion", + "summary": "$appendConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppendVersionRequest" + } + } + } + }, + "responses": { + "201": { + "description": "The version, as appended, together with anything the write moved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WrittenVariantVersion" + } + } + } + }, + "400": { + "description": "The version cannot be appended as described: the body would change the variant's conditions,\nor `valid_from` is not a timestamp this store can sort by.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "No such schema, or no such variant under it (`NOT_FOUND`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "The variant already has a version at that `valid_from` (`VERSION_CONFLICT`) — append means\nappend, never an implicit overwrite. Replace or patch that version instead.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + } + }, + "/v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}/versions/{valid_from}": { + "get": { + "description": "Returns one specific version of a variant, by the instant it takes effect — what a form editing\nthat version loads.\n\nExact, never nearest: an instant the variant has no version at is a not-found rather than the\nversion that would be in effect at it. That question is the shorthand read's, or `:resolve`'s.\n", + "operationId": "$getConditionalVariantVersion", + "summary": "$getConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + }, + { + "in": "path", + "name": "valid_from", + "description": "The version to address, by the instant it takes effect.\n\nAn RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time\n(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any accepted way: it is\ncanonicalized before it is matched, so the spelling a read returned and the spelling a\nhuman typed address the same version.\n", + "schema": { + "type": "string" + }, + "required": true, + "example": "2027-01-01T00:00:00.000Z" + } + ], + "responses": { + "200": { + "description": "The version, as stored", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VariantVersion" + } + } + } + }, + "400": { + "description": "Invalid request, e.g. a `valid_from` that is not a timestamp this store can sort by.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "This variant has no version at that instant (`NOT_FOUND`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + }, + "put": { + "description": "Replaces one version's values wholesale, addressed by its `valid_from`.\n\nEditable whatever its date, at both ends of the timeline: a scheduled version must stay\neditable so a staged price can be corrected before it goes live rather than accumulating dead\nversions beside it, and a past one must stay editable because correcting history is ordinary\nwork. Writing a superseded version is answered with a warning naming what a past-dated read now\nreturns; it is not refused.\n\nAttributes the variant may not override are ignored where the body carries them, and their\nstored value is preserved rather than dropped.\n", + "operationId": "$replaceConditionalVariantVersion", + "summary": "$replaceConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + }, + { + "in": "path", + "name": "valid_from", + "description": "The version to address, by the instant it takes effect.\n\nAn RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time\n(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any accepted way: it is\ncanonicalized before it is matched, so the spelling a read returned and the spelling a\nhuman typed address the same version.\n", + "schema": { + "type": "string" + }, + "required": true, + "example": "2027-01-01T00:00:00.000Z" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceVersionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The version, as the write left it, together with anything the write moved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WrittenVariantVersion" + } + } + } + }, + "400": { + "description": "The write cannot be applied as described: it would move the version it addresses to another\n`valid_from`, or change the conditions its variant is pinned to — both identity rather than\ncontent, and both fixed at creation. Also when `_revision` is missing or is not a revision\nmarker.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "This variant has no version at that instant (`NOT_FOUND`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "The version has been written since `_revision` was read (`WRITE_CONFLICT`, retryable after\nre-reading the version).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + }, + "patch": { + "description": "Changes only the fields it names on one version, addressed by its `valid_from`.\n\nEverything the body does not mention is left as stored. A partial update that tries to change a\npinned condition value is refused: condition values are immutable after a variant is created,\nand this is the path that rule is most likely to be broken on by accident.\n", + "operationId": "$patchConditionalVariantVersion", + "summary": "$patchConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + }, + { + "in": "path", + "name": "valid_from", + "description": "The version to address, by the instant it takes effect.\n\nAn RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time\n(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any accepted way: it is\ncanonicalized before it is matched, so the spelling a read returned and the spelling a\nhuman typed address the same version.\n", + "schema": { + "type": "string" + }, + "required": true, + "example": "2027-01-01T00:00:00.000Z" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchVersionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The version, as the write left it, together with anything the write moved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WrittenVariantVersion" + } + } + } + }, + "400": { + "description": "The write cannot be applied as described: it would move the version it addresses to another\n`valid_from`, or change the conditions its variant is pinned to — both identity rather than\ncontent, and both fixed at creation. Also when `_revision` is missing or is not a revision\nmarker.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "This variant has no version at that instant (`NOT_FOUND`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "The version has been written since `_revision` was read (`WRITE_CONFLICT`, retryable after\nre-reading the version).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + }, + "delete": { + "description": "Removes one version of a variant.\n\nWithdrawing a scheduled adjustment is what this is for, and deleting a future version warns\nabout nothing — nothing that has resolved, or could have resolved, changes. Deleting a version\nthat has taken effect is allowed too and answered with a warning: it changes what a past-dated\nread returns, and if it was the version in effect it changes what resolves now.\n\n**A variant's last remaining version cannot be deleted.** Such a variant would still hold its\ncondition tuple and still be selectable, and then resolve to nothing — which is a variant delete\nwearing a version delete's clothes. Delete the variant instead; that frees the tuple too.\n\nThe variant itself is untouched: it keeps its conditions, its tuple and its place in the index.\n", + "operationId": "$deleteConditionalVariantVersion", + "summary": "$deleteConditionalVariantVersion", + "tags": [ + "Conditional Pricing API" + ], + "parameters": [ + { + "in": "path", + "name": "slug", + "description": "The conditional entity type this variant belongs to", + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "required": true, + "example": "price" + }, + { + "in": "path", + "name": "entity_id", + "description": "The conditional entity the variant belongs to", + "schema": { + "type": "string" + }, + "required": true, + "example": "price-sp26d1yo" + }, + { + "in": "path", + "name": "variant_id", + "description": "The variant whose timeline this call addresses", + "schema": { + "type": "string" + }, + "required": true, + "example": "var-46045" + }, + { + "in": "path", + "name": "valid_from", + "description": "The version to address, by the instant it takes effect.\n\nAn RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time\n(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any accepted way: it is\ncanonicalized before it is matched, so the spelling a read returned and the spelling a\nhuman typed address the same version.\n", + "schema": { + "type": "string" + }, + "required": true, + "example": "2027-01-01T00:00:00.000Z" + }, + { + "in": "query", + "name": "_revision", + "description": "The revision marker read from the version being deleted. The delete is refused if the\nversion has been written since.\n\nA query parameter rather than a body field, since a DELETE carrying a body travels badly\nthrough clients and proxies; it is the same marker the write bodies carry as `_revision`.\n", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "example": 3 + } + ], + "responses": { + "200": { + "description": "The version removed, together with anything the delete moved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeletedVariantVersion" + } + } + } + }, + "400": { + "description": "The version cannot be removed: it is the variant's only one, `_revision` is missing, or\n`valid_from` is not a timestamp this store can sort by.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "404": { + "description": "This variant has no version at that instant (`NOT_FOUND`).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + }, + "409": { + "description": "The version has been written since `_revision` was read (`WRITE_CONFLICT`, retryable after\nre-reading the version).\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConditionalPricingError" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "description": "Epilot Bearer Token" + }, + "EpilotPublicAuth": { + "type": "http", + "scheme": "bearer", + "description": "Epilot Public Access Bearer Token", + "bearerFormat": "JWT" + } + }, + "schemas": { + "IntegrationId": { + "type": "string", + "enum": [ + "getag", + "external-catalog" + ] + }, + "ConditionalEntitySlug": { + "type": "string", + "description": "Schema slug of an entity type that can be conditional — the `{slug}` of every\nconditional-pricing route.\n", + "enum": [ + "product", + "price", + "coupon" + ] + }, + "ConditionType": { + "type": "string", + "description": "The kind of value a condition holds, which decides how a variant's pinned value is matched\nagainst a resolve context.\n\n- `string`: an arbitrary string, matched exactly and case-sensitively\n- `number`: a numeric value\n- `date`: a single date\n- `daterange`: a window with a from and an until timestamp; both ends may be left open\n- `boolean`: a true/false value\n- `select`: one of the values declared in `options`, unless `allow_any` is set\n- `location`: a geographic value, shaped by `format`\n\nThere is no condition type for the fallback variant. Being the entity's fallback is a\nproperty of the variant, set by the `default` flag on a variant write, and needs nothing\ndeclared in the schema.\n", + "enum": [ + "string", + "number", + "date", + "daterange", + "boolean", + "select", + "location" + ] + }, + "ConditionDefinition": { + "type": "object", + "description": "One condition dimension, in the shape a schema's `conditions` array holds it — copy it in\nverbatim.\n", + "required": [ + "name", + "label", + "type" + ], + "properties": { + "name": { + "type": "string", + "description": "How variants and resolve contexts refer to this condition. Independent of attribute\nnames: a value needed as an attribute too is duplicated onto the variant.\n\n`default`, and any name beginning with `_`, are reserved for the server: a condition\ndeclared under one is ignored, since nothing could pin it and no context could address it.\n", + "example": "postal_code" + }, + "label": { + "type": "string", + "description": "Human-readable name of the condition.", + "example": "Postal Code" + }, + "type": { + "$ref": "#/components/schemas/ConditionType" + }, + "options": { + "type": "array", + "description": "The declared vocabulary of a `select` condition. Absent for every other type.\n\nThe same shape a `select` attribute's `options` has on the Entity API, item for item: an\nentry is either the value itself or an object carrying that value and an optional display\n`title`. A `title` is never pinned by a variant and never matched — two entries differing\nonly in their title are one vocabulary entry.\n\nEnforced on variant writes, unless `allow_any` is true: a pinned value outside the\nvocabulary is rejected with `CONDITION_VALUE_INVALID`. It is *not* enforced on resolve —\na vocabulary says what may be stored, not what may be asked for, so a context value\noutside it is a query that simply matches nothing.\n", + "items": { + "anyOf": [ + { + "type": "string", + "nullable": true + }, + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + }, + "title": { + "type": "string" + } + } + } + ] + }, + "example": [ + "private", + "commercial" + ] + }, + "allow_any": { + "type": "boolean", + "description": "Allow arbitrary stored values in addition to the declared `options`. Absent means strict:\na variant may only pin a declared option.\n", + "example": false + }, + "format": { + "type": "string", + "description": "The value shape of a `location` condition. Absent for every other type.", + "enum": [ + "zipcode", + "zipcode + town" + ] + } + } + }, + "ConditionSet": { + "type": "object", + "description": "A named bundle of condition definitions, built in for one entity type.", + "required": [ + "id", + "label", + "description", + "conditions" + ], + "properties": { + "id": { + "type": "string", + "description": "Identifies the set within this entity type's catalog.", + "example": "delivery_area" + }, + "label": { + "type": "string", + "description": "Human-readable name of the set.", + "example": "Delivery Area" + }, + "description": { + "type": "string", + "description": "What the set is for, and when to reach for it." + }, + "conditions": { + "type": "array", + "description": "The condition definitions to copy into the schema's own `conditions` array.", + "items": { + "$ref": "#/components/schemas/ConditionDefinition" + } + } + } + }, + "ConditionSetCatalog": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "description": "The condition sets built in for the requested entity type, in the order they are offered.\n", + "items": { + "$ref": "#/components/schemas/ConditionSet" + } + } + } + }, + "ConditionalPricingErrorCode": { + "type": "string", + "description": "Machine-readable failure mode of a conditional-pricing operation, allowing clients\nto branch on the kind of failure instead of parsing the error message.\n\n- `NOT_FOUND` (404): the addressed entity, variant or version does not exist\n- `AMBIGUOUS_RESOLUTION` (409): several variants match the given context while a single result was requested\n- `TUPLE_CONFLICT` (409): the condition tuple is already claimed by another variant\n- `VERSION_CONFLICT` (409): a version already exists at the given `valid_from` on that variant\n- `CONDITION_UNDEFINED` (400): the context names a condition the entity's schema does not define\n- `OPERATOR_UNSUPPORTED` (400): the requested operator is not applicable to the condition's type\n- `CONTEXT_FORMAT_INVALID` (400): a context value is malformed for its condition type\n- `CONDITION_VALUE_INVALID` (400): a variant write pins a `select` value the condition's declared `options` do not contain\n- `TOO_MANY_MATCHES` (400): a multi-match resolve exceeded its result cap\n- `WRITE_CONFLICT` (409): transient write contention, retryable unlike `TUPLE_CONFLICT`\n\nEach code is emitted with the HTTP status shown above, and only with that status.\n", + "enum": [ + "NOT_FOUND", + "AMBIGUOUS_RESOLUTION", + "TUPLE_CONFLICT", + "VERSION_CONFLICT", + "CONDITION_UNDEFINED", + "OPERATOR_UNSUPPORTED", + "CONTEXT_FORMAT_INVALID", + "CONDITION_VALUE_INVALID", + "TOO_MANY_MATCHES", + "WRITE_CONFLICT" + ] + }, + "ResolveConditionalEntityRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "entity_id" + ], + "properties": { + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "entity_id": { + "type": "string", + "description": "The conditional entity to resolve. Resolution is always scoped to exactly one.", + "example": "price-sp26d1yo" + }, + "context": { + "$ref": "#/components/schemas/ResolveContext" + }, + "as_of": { + "type": "string", + "description": "The instant the version is selected at — the version with the latest `valid_from` at or\nbefore it. Defaults to now. A variant whose first version is later than this is\nscheduled rather than applicable, and is excluded from resolution entirely.\n\nAn RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time\n(`2026-01-01T00:00:00Z`), to at most millisecond precision. Deliberately not declared as\n`format: date-time`, which would reject the plain-date form that this accepts.\n", + "example": "2027-03-15T00:00:00Z" + }, + "options": { + "$ref": "#/components/schemas/ResolveOptions" + } + } + }, + "ResolveContext": { + "type": "object", + "additionalProperties": true, + "description": "The situation to resolve for: a flat map keyed by condition name, as the entity's schema\ndeclares them. A condition left out of the map is not a wildcard — it matches only variants\nthat leave that condition unpinned.\n\nEach value is either an exact value, typed by its condition, or a single-operator predicate\nobject:\n\n- `{ \"lt\": v }`, `{ \"lte\": v }`, `{ \"gt\": v }`, `{ \"gte\": v }` — order against a `number` or\n `date` condition.\n- `{ \"in\": [...] }` — membership, against a `string`, `select` or `number` condition.\n- `{ \"between\": \"2026-03-01\" }` — the explicit spelling of `daterange` containment; a plain\n date supplied for a `daterange` condition means the same thing.\n- `{ \"exists\": true }` — pinned to any value. `{ \"exists\": false }` says what leaving the key\n out says.\n\nExact values are typed by their condition: a `string` or `select` matches exactly and\ncase-sensitively, with no trimming; a `location` of format `zipcode` is the postal code\nitself, and one of format `zipcode + town` an object carrying both, whose town is compared\ncase- and whitespace-insensitively while its postal code is not.\n\n`default`, and any name beginning with `_`, are reserved for the server and cannot be\nsupplied here.\n", + "example": { + "postal_code": "46045", + "consumption": { + "lt": 5000 + } + } + }, + "ResolveOptions": { + "type": "object", + "additionalProperties": false, + "properties": { + "resolve_one": { + "type": "boolean", + "default": false, + "description": "Ask for an unambiguous answer. Several applicable variants become `AMBIGUOUS_RESOLUTION`\nrather than a set, and nothing applicable becomes `NOT_FOUND` rather than an empty one.\nThe response shape does not change: `results` simply carries exactly one entry.\n" + } + } + }, + "ResolvedVariants": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "description": "One composed payload per applicable variant, capped at 100 — a context selecting more\nthan that is answered with `TOO_MANY_MATCHES` instead, since each result costs its own\nversion lookup. No dominance or specificity ordering is applied between them.\n", + "items": { + "$ref": "#/components/schemas/ResolvedVariant" + } + } + } + }, + "ResolvedVariant": { + "type": "object", + "additionalProperties": true, + "description": "The entity as this variant leaves it — every attribute of a plain entity read, with the\napplicable version's overrides applied — plus the discriminators saying where the numbers\ncame from.\n", + "required": [ + "_id", + "_variant_id", + "_version_valid_from", + "_conditions" + ], + "properties": { + "_id": { + "type": "string", + "description": "The logical entity's id — the same one a plain entity read returns. Resolution never\nmints a new identity; a variant is a set of values for *this* entity, not another one.\n", + "example": "price-sp26d1yo" + }, + "_variant_id": { + "type": "string", + "description": "The variant these values came from. Durable: this is what an order or a contract pins to\nread the same numbers back later.\n", + "example": "var-46045" + }, + "_version_valid_from": { + "type": "string", + "description": "The `valid_from` of the version applied for the requested `as_of`.", + "example": "2027-01-01T00:00:00.000Z" + }, + "_conditions": { + "allOf": [ + { + "$ref": "#/components/schemas/VariantConditions" + } + ], + "description": "The conditions this variant pins, plus the boolean `default` discriminator.\n\nUnderscore-prefixed, like every other discriminator here, so that it cannot collide with\nan attribute an organization happens to have called `conditions`.\n" + } + } + }, + "CreateVariantRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "values" + ], + "properties": { + "conditions": { + "$ref": "#/components/schemas/PinnedConditions" + }, + "default": { + "type": "boolean", + "default": false, + "description": "Mark this variant as the entity's fallback: the one served when no other variant applies.\n\nA property of the variant, never an entry in `conditions` — a variant claiming a value for\nthe marker would hold a real condition tuple while being permanently unmatchable, since no\nresolve context ever supplies it. A default variant cannot pin anything else, and an\nentity can have at most one.\n\nAvailable to every conditional entity: nothing has to be declared in the schema first.\nThe variant is stored pinning one reserved condition, which is what makes the ordinary\ncondition-tuple guard enforce at-most-one-per-entity with no rule of its own.\n" + }, + "valid_from": { + "type": "string", + "description": "When the first version takes effect. Defaults to now.\n\nAn RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time\n(`2026-01-01T00:00:00Z`), to at most millisecond precision. Deliberately not declared as\n`format: date-time`, which would reject the plain-date form that this accepts.\n", + "example": "2027-01-01T00:00:00Z" + }, + "values": { + "$ref": "#/components/schemas/VariantValues" + } + } + }, + "VariantConditions": { + "type": "object", + "additionalProperties": true, + "required": [ + "default" + ], + "description": "A variant's pinned conditions as a reader sees them: the pins the schema declares, plus a\nboolean `default` saying whether this is the entity's fallback.\n\n`default` is always present and always a boolean, so a client can branch on \"did I get the\nfallback?\" without knowing how one is stored. The reserved condition a fallback is actually\npinned under never appears here.\n", + "properties": { + "default": { + "type": "boolean" + } + }, + "example": { + "postal_code": "46045", + "default": false + } + }, + "PinnedConditions": { + "type": "object", + "additionalProperties": true, + "description": "The situation this variant applies to: a flat map keyed by condition name, as the entity's\nschema declares them. A condition left out is a wildcard — the variant applies whatever the\ncontext says for it, which is what makes adding a condition to a schema non-breaking for the\nvariants that already exist.\n\nExact values only. Predicates are accepted in a resolve context and nowhere else, so that\nmatching is decided in exactly one place.\n\nValues are typed by their condition and stored canonicalized for that type: a `date` becomes\nmillisecond-precision UTC, a `daterange` an object carrying `from` and `until` where an empty\nstring is an open end, a `location` of format `zipcode` the postal code itself and one of\nformat `zipcode + town` an object carrying both. A `select` value must be a string, and must\nbe one the condition's `options` declare unless it sets `allow_any`.\n\n`default`, and any name beginning with `_`, are reserved for the server and cannot be pinned\nhere. Whether a variant is the entity's fallback is set through the request's `default` flag.\n", + "example": { + "postal_code": "46045" + } + }, + "VariantValues": { + "type": "object", + "additionalProperties": true, + "description": "The attribute values this version overrides on the base entity, keyed by attribute name.\n\nOnly attributes currently declaring `overridable_attribute` are applied. Metadata fields\n(anything underscore-prefixed), readonly attributes, hidden attributes and non-overridable\nattributes present here are ignored rather than rejected, so a client working from a slightly\nstale schema snapshot still succeeds instead of failing on fields it could not have known to\ndrop. An attribute's `render_condition` says when to show it and has no bearing on whether a\nvariant may override it.\n\nIgnored means *not updated*, never *removed*: a value already stored for an attribute that is\nnot currently overridable is preserved, so removing and restoring the flag deactivates and\nthen reactivates the same override.\n\nA composite price's `price_components` is an ordinary overridable relation attribute: a\ncomposite variant pins its component variants here the same way any other relation value is\nset, with no special handling.\n", + "example": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + } + }, + "CreatedVariant": { + "type": "object", + "required": [ + "variant_id", + "entity_id", + "schema", + "conditions", + "valid_from", + "values", + "_created_at", + "_updated_at", + "_revision", + "warnings" + ], + "properties": { + "variant_id": { + "type": "string", + "description": "Server-generated, always. This is the durable key orders and contracts pin, so it is never\naccepted from a client — a client-suppliable id would risk collisions between independent\nimporters.\n", + "example": "var-46045" + }, + "entity_id": { + "type": "string", + "example": "price-sp26d1yo" + }, + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "conditions": { + "allOf": [ + { + "$ref": "#/components/schemas/VariantConditions" + } + ], + "description": "The situation this variant applies to, plus the boolean `default` discriminator — the\nsame shape `_conditions` has on a resolved payload.\n" + }, + "valid_from": { + "type": "string", + "description": "When the first version takes effect, canonicalized to millisecond-precision UTC.", + "example": "2027-01-01T00:00:00.000Z" + }, + "values": { + "$ref": "#/components/schemas/VariantValues" + }, + "_created_at": { + "type": "string", + "description": "When the first version was created.", + "readOnly": true + }, + "_updated_at": { + "type": "string", + "description": "When the first version was last written.", + "readOnly": true + }, + "_revision": { + "type": "number", + "description": "The revision a later write to this version must carry to be accepted. Genuinely current,\nunlike one read back later from an eventually-consistent read.\n", + "readOnly": true + }, + "warnings": { + "type": "array", + "description": "Things worth knowing that did not stop the write. Empty in the ordinary case — a client\nreads its length rather than branching on its absence.\n", + "items": { + "$ref": "#/components/schemas/VariantWriteWarning" + } + } + } + }, + "VariantWriteWarning": { + "type": "object", + "required": [ + "code", + "message", + "variant_count", + "cap" + ], + "properties": { + "code": { + "type": "string", + "description": "- `VARIANT_COUNT_APPROACHING_CAP`: this entity is nearing the number of variants it may\n hold. Surfaced rather than rejected, so an importer finds out with a whole run's notice\n instead of discovering the limit halfway through a refresh.\n", + "enum": [ + "VARIANT_COUNT_APPROACHING_CAP" + ] + }, + "message": { + "type": "string" + }, + "variant_count": { + "type": "number", + "description": "Variants this entity holds, including the one just created." + }, + "cap": { + "type": "number", + "description": "Variants this entity may hold. Configurable per organization." + } + } + }, + "DeletedVariant": { + "type": "object", + "required": [ + "variant_id", + "entity_id", + "schema", + "tuple_released", + "versions_deleted" + ], + "properties": { + "variant_id": { + "type": "string", + "example": "var-46045" + }, + "entity_id": { + "type": "string", + "example": "price-sp26d1yo" + }, + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "tuple_released": { + "type": "boolean", + "description": "Whether this call is the one that freed the variant's combination of condition values.\n`false` where an earlier, interrupted attempt had already freed it — the delete still\nsucceeded, and the combination was already reusable.\n" + }, + "versions_deleted": { + "type": "number", + "description": "Version rows this call removed." + } + } + }, + "VariantVersion": { + "type": "object", + "description": "One version of one variant: the attribute overrides it carries, the instant it takes effect,\nand the variant it belongs to.\n\nThese are the version's **own** overrides, not the base entity overlaid with them — this is\nwhat an editing screen loads and saves, and what it edits is the overrides. Composing them onto\nthe entity is what `:resolve` answers.\n", + "required": [ + "variant_id", + "entity_id", + "schema", + "conditions", + "valid_from", + "values", + "_created_at", + "_updated_at", + "_revision" + ], + "properties": { + "variant_id": { + "type": "string", + "example": "var-46045" + }, + "entity_id": { + "type": "string", + "example": "price-sp26d1yo" + }, + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "conditions": { + "allOf": [ + { + "$ref": "#/components/schemas/VariantConditions" + } + ], + "description": "The situation the variant applies to, plus the boolean `default` discriminator. A property\nof the variant rather than of this version: every version of a variant carries the same\none, and no version write can change it.\n" + }, + "valid_from": { + "type": "string", + "description": "When this version takes effect, canonicalized to millisecond-precision UTC. A version's\nidentity within its variant — it never moves.\n", + "example": "2027-01-01T00:00:00.000Z" + }, + "values": { + "$ref": "#/components/schemas/VariantValues" + }, + "_created_at": { + "type": "string", + "description": "When this version was created.", + "readOnly": true + }, + "_updated_at": { + "type": "string", + "description": "When this version was last written.", + "readOnly": true + }, + "_revision": { + "type": "integer", + "description": "The revision a write to this version must carry to be accepted. Always current: every read\nthat returns one is strongly consistent, so it is never a marker a write would be refused\nfor having read too early.\n", + "readOnly": true, + "example": 3 + } + } + }, + "WrittenVariantVersion": { + "description": "A version as a write left it, together with anything the write moved.\n", + "allOf": [ + { + "$ref": "#/components/schemas/VariantVersion" + }, + { + "type": "object", + "required": [ + "warnings" + ], + "properties": { + "warnings": { + "type": "array", + "description": "What this write moved, if anything. Empty in the ordinary case — a client reads its\nlength rather than branching on its absence.\n", + "items": { + "$ref": "#/components/schemas/VersionWriteWarning" + } + } + } + } + ] + }, + "DeletedVariantVersion": { + "type": "object", + "required": [ + "variant_id", + "entity_id", + "schema", + "valid_from", + "warnings" + ], + "properties": { + "variant_id": { + "type": "string", + "example": "var-46045" + }, + "entity_id": { + "type": "string", + "example": "price-sp26d1yo" + }, + "schema": { + "$ref": "#/components/schemas/ConditionalEntitySlug" + }, + "valid_from": { + "type": "string", + "description": "The version removed, canonicalized to millisecond-precision UTC.", + "example": "2027-01-01T00:00:00.000Z" + }, + "warnings": { + "type": "array", + "description": "What the delete moved, if anything. Empty when a scheduled version was withdrawn.", + "items": { + "$ref": "#/components/schemas/VersionWriteWarning" + } + } + } + }, + "VersionWriteWarning": { + "type": "object", + "description": "Something a version write moved. A version write is never refused for being late — backdating a\nversion, and editing or deleting one that has already been superseded, are both accepted — so\nwhat a caller gets instead is a warning naming exactly what changed. One write can carry both\ncodes.\n", + "required": [ + "code", + "message", + "valid_from" + ], + "properties": { + "code": { + "type": "string", + "description": "- `ACTIVE_VERSION_REPLACED`: what resolves **now** changed, other than by a newer version\n taking effect. The version in effect was written behind, or removed.\n- `SUPERSEDED_VERSION_WRITTEN`: what a past-dated (`as_of`) read returns changed. The write\n landed on, or created, a version that is not the one currently in effect.\n", + "enum": [ + "ACTIVE_VERSION_REPLACED", + "SUPERSEDED_VERSION_WRITTEN" + ] + }, + "message": { + "type": "string" + }, + "valid_from": { + "type": "string", + "description": "The version this write created, changed or removed.", + "example": "2026-08-01T00:00:00.000Z" + }, + "active_valid_from": { + "type": "string", + "description": "The version in effect when the write landed, before it did. Absent when the variant had\nnone — every version of it still scheduled.\n", + "example": "2026-01-01T00:00:00.000Z" + } + } + }, + "AppendVersionRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "values" + ], + "properties": { + "valid_from": { + "type": "string", + "description": "When this version takes effect. Defaults to now.\n\nAn RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time\n(`2026-01-01T00:00:00Z`), to at most millisecond precision. Deliberately not declared as\n`format: date-time`, which would reject the plain-date form that this accepts.\n\nA date in the past is accepted and answered with warnings, never refused. A date the\nvariant already has a version at is refused as `VERSION_CONFLICT`.\n\n**Omit this to mean \"now\"** — that is the only spelling of now that is reliably silent. A\ntimestamp taken from the caller's own clock is already some milliseconds old when the\nserver judges it, which makes it a backdate, however small, and it is answered with the\nwarnings a backdate earns.\n", + "example": "2027-01-01T00:00:00Z" + }, + "values": { + "$ref": "#/components/schemas/VariantValues" + }, + "conditions": { + "allOf": [ + { + "$ref": "#/components/schemas/PinnedConditions" + } + ], + "description": "Optional, and never applied: a variant's conditions are fixed when it is created. Accepted\nonly so that a client building its body from the version it loaded is not forced to strip\nthem out, and refused when they describe a different situation from the stored one.\n" } + } + }, + "ReplaceVersionRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "values", + "_revision" ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductRecommendationSearch" - }, - "examples": { - "Search product recommendations by id": { - "value": { - "product_recommendation_ids": [ - "22c134b7-8dd8-4872-a5c1-8fc6dd4ac2ee" - ] - } - }, - "Search product recommendations by id with location filter": { - "value": { - "product_recommendation_ids": [ - "22c134b7-8dd8-4872-a5c1-8fc6dd4ac2ee" - ], - "filters": { - "location": { - "postal_code": "50670", - "city": "Köln" - } - } - } - }, - "Search product recommendations by source product and price": { - "value": { - "catalog_item": { - "product_id": "aa1ebf85-f36e-46ff-978c-ed948c646f12", - "price_id": "4ecf9a61-a974-4594-bae2-bcb798f54d76" - } - } - }, - "Search product recommendations by contract": { - "value": { - "contract_id": "98ffca40-46ab-4288-af10-a478893b2aa2" - } - } + "properties": { + "values": { + "allOf": [ + { + "$ref": "#/components/schemas/VariantValues" } - } + ], + "description": "The complete set of attribute overrides this version carries. An overridable attribute\nabsent from here stops being overridden.\n\nAttributes the variant may not override are ignored where this carries them, and their\n**stored value is kept rather than dropped** — otherwise a routine full-snapshot write\nwould erase an override the moment its attribute's `overridable_attribute`, `readonly` or\n`hidden` flag happened to be off.\n" + }, + "_revision": { + "type": "integer", + "minimum": 1, + "description": "The revision marker read from the version being written. The write is refused with\n`WRITE_CONFLICT` if the version has been written since.\n\nRequired rather than optional: an optional one is a guarantee every client can opt out of\nby forgetting a field, and the write it protects is the one that overwrites somebody\nelse's edit.\n", + "example": 3 + }, + "valid_from": { + "type": "string", + "description": "Optional, and never applied. Accepted when it names the version being addressed — so a\nclient building its body from what it loaded need not strip it out — and refused when it\nnames another: a version's `valid_from` is its identity, and moving it is an append and a\ndelete rather than an edit.\n" + }, + "conditions": { + "allOf": [ + { + "$ref": "#/components/schemas/PinnedConditions" + } + ], + "description": "Optional, and never applied: a variant's conditions are fixed when it is created. Refused\nwhen they describe a different situation from the stored one.\n" } - }, - "responses": { - "200": { - "description": "The product recommendations result", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductRecommendationResponse" - }, - "examples": { - "Product recommendations": { - "value": { - "hits": 1, - "results": [ - { - "_title": "Tarifwechsel 2025", - "_created_at": "2025-01-29T15:46:41.014Z", - "_updated_at": "2025-01-29T15:46:41.014Z", - "name": "Tarifwechsel 2025", - "type": "change", - "source_product": { - "$relation": [ - { - "entity_id": "e2b12b54-d904-42b7-ac44-802d8ebaadf4" - } - ] - }, - "source_price": { - "$relation": [ - { - "entity_id": "255877f3-f9c2-483e-9c71-89088c142bfc" - } - ] - }, - "_schema": "product_recommendation", - "offers": [ - { - "target_id": "f9aa8757-505b-46bd-9310-605d64849565", - "items": [ - { - "product_id": "70ad8a08-46c2-473b-b1c5-8bccfb85e452", - "price_id": "26c9c39d-db4a-4132-af2e-86ab5c7b156c" - }, - { - "product_id": "dd97a2a4-e48b-43c5-8717-7ac563c67e05", - "price_id": "4ecf9a61-a974-4594-bae2-bcb798f54d76" - }, - { - "product_id": "aa1ebf85-f36e-46ff-978c-ed948c646f12", - "price_id": "4ecf9a61-a974-4594-bae2-bcb798f54d76" - } - ] - } - ], - "_id": "6d872912-ef8a-4815-b9db-01744c0828ac", - "_org": "739224" - } - ] - } - } - } + } + }, + "PatchVersionRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "values", + "_revision" + ], + "properties": { + "values": { + "allOf": [ + { + "$ref": "#/components/schemas/VariantValues" } - } + ], + "description": "Only the attribute overrides to change. Everything not mentioned is left as stored.\n\n`null` is a value like any other here rather than a deletion; to stop overriding an\nattribute, send the complete snapshot without it through the replace operation.\n" }, - "400": { - "description": "Invalid payload", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + "_revision": { + "type": "integer", + "minimum": 1, + "description": "The revision marker read from the version being written. The write is refused with\n`WRITE_CONFLICT` if the version has been written since.\n", + "example": 3 + }, + "valid_from": { + "type": "string", + "description": "Optional, never applied, and refused when it names a version other than the one addressed." + }, + "conditions": { + "allOf": [ + { + "$ref": "#/components/schemas/PinnedConditions" } - } + ], + "description": "Optional, and never applied. A partial update that tries to change a pinned condition value\nis refused — this is the path that rule is most likely to be broken on by accident.\n" } } - } - } - }, - "components": { - "securitySchemes": { - "EpilotAuth": { - "type": "http", - "scheme": "bearer", - "description": "Epilot Bearer Token" - }, - "EpilotPublicAuth": { - "type": "http", - "scheme": "bearer", - "description": "Epilot Public Access Bearer Token", - "bearerFormat": "JWT" - } - }, - "schemas": { - "IntegrationId": { - "type": "string", - "enum": [ - "getag", - "external-catalog" - ] }, "Error": { "required": [ @@ -3449,6 +5157,31 @@ } } }, + "ConditionalPricingError": { + "description": "An error from a conditional-pricing operation, carrying a machine-readable `code`\nfrom the conditional-pricing vocabulary plus any structured data about the failure,\nso a client can branch on the kind of failure rather than parse the message.\nReferenced only by the operations that emit these codes; every other operation\nkeeps the plain `Error` shape.\n", + "allOf": [ + { + "$ref": "#/components/schemas/Error" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "The error message. Carries the same string as `message`, which the shared `Error`\nschema requires — `error` is the field responses have always used, and every caller\nto date reads. Declared here rather than on the shared `Error` because a request\nvalidation failure puts a list of validation errors in this field instead of a\nstring, and those responses reference `Error` directly.\n" + }, + "code": { + "$ref": "#/components/schemas/ConditionalPricingErrorCode" + }, + "details": { + "type": "object", + "additionalProperties": true, + "description": "Structured data about the failure, shaped by the accompanying `code`\n(e.g. the candidate variants of an `ambiguous-resolution`). Only present\nwhen the failure has structured data to report, and never without a `code`.\n" + } + } + } + ] + }, "Product": { "type": "object", "description": "The product entity", @@ -5569,6 +7302,9 @@ "description": "When true, enables entity hydration to resolve nested $relation references in-place.", "default": false }, + "fields": { + "$ref": "#/components/schemas/CatalogFieldsParam" + }, "availability": { "$ref": "#/components/schemas/AvailabilityFilters" } @@ -5583,6 +7319,17 @@ "q" ] }, + "CatalogFieldsParam": { + "type": "array", + "description": "List of entity fields to include or exclude from the results.\n", + "items": { + "type": "string" + }, + "example": [ + "!_files", + "!**.versions" + ] + }, "CatalogSearchResult": { "description": "The query result payload", "type": "object", @@ -6953,7 +8700,14 @@ "description": "The deprecated tax rate applied.\nThis field has been deprecated in favor of the new Tax Management. You should use the new tax fields pointing to a proper tax entity.\n" }, "tax": { - "$ref": "#/components/schemas/Tax" + "oneOf": [ + { + "$ref": "#/components/schemas/Tax" + }, + { + "$ref": "#/components/schemas/TaxItem" + } + ] } } }, @@ -7687,6 +9441,9 @@ "oneOf": [ { "$ref": "#/components/schemas/Tax" + }, + { + "$ref": "#/components/schemas/TaxItem" } ] } @@ -8429,6 +10186,37 @@ "_updated_at": "2021-02-09T12:41:43.662Z" } }, + "TaxItem": { + "type": "object", + "description": "A minimal, ad-hoc tax rate for line items with no backing tax entity\nin the catalog (e.g. a fully custom/composite price component built\nby a client with no product/price reference to resolve tax from).\nMirrors how PriceItem relates to Price: unlike Tax, this has no\nentity identity — it isn't persisted and can't be looked up by _id,\nso it can't be shared/reused across price items the way a catalog\nTax can.\n", + "additionalProperties": false, + "required": [ + "type", + "rate" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "VAT", + "GST", + "Custom" + ] + }, + "rate": { + "type": "number", + "nullable": true + }, + "description": { + "type": "string" + } + }, + "example": { + "rate": 19, + "type": "VAT", + "description": "Custom 19% VAT" + } + }, "TaxBreakdownInfo": { "type": "object", "properties": { diff --git a/packages/cli/definitions/snapshot.json b/packages/cli/definitions/snapshot.json index fe20ec74c..6ca157288 100644 --- a/packages/cli/definitions/snapshot.json +++ b/packages/cli/definitions/snapshot.json @@ -27,6 +27,10 @@ { "name": "Snapshots", "description": "Snapshot CRUD and restore operations" + }, + { + "name": "ScheduledSnapshots", + "description": "Enrollment and configuration for scheduled org snapshots" } ], "paths": { @@ -103,6 +107,20 @@ "pattern": "^[^:]+:.+$" } } + }, + { + "in": "query", + "name": "trigger", + "description": "Filter to snapshots with a specific trigger. Uses the `byTrigger` GSI\nfor an efficient indexed query — no table scan. Only snapshots created\nafter the GSI was added carry this index entry; pre-existing rows will\nnot appear in trigger-filtered results.\n", + "schema": { + "type": "string", + "enum": [ + "manual", + "sync", + "blueprint_install", + "scheduled" + ] + } } ], "responses": { @@ -146,7 +164,7 @@ "post": { "operationId": "captureOrgSnapshot", "summary": "captureOrgSnapshot", - "description": "Snapshot the caller's whole organization now. Fetches a fresh inventory\nof the org's configuration resources from configuration-hub-api, persists\nit as an inventory artifact, and starts a `scope: \"org\"` chunked capture.\nAsync — returns immediately with a snapshot ID; client polls `getSnapshot`\nand watches `capture_summary` fill in until `create.status` moves from\n`in_progress` to `completed` or `failed`.\n\nSensitive types (`access_token`, `environment_variable`), types with no\nengine adapter, and any `excluded_types` are dropped from the capture and\nrecorded in the snapshot's coverage report.\n", + "description": "Snapshot the caller's whole organization now. Creates a `scope: \"org\"`\nsnapshot row and starts a chunked capture Step Function, then returns\nimmediately. The capture asynchronously fetches a fresh inventory of the\norg's configuration resources from configuration-hub-api, persists it as\nan inventory artifact, and captures each resource. Client polls\n`getSnapshot` and watches `capture_summary` fill in until `create.status`\nmoves from `in_progress` to `completed` or `failed`. An org with no\ncapturable resources finalizes as a completed 0-resource snapshot.\n\nSensitive types (`access_token`, `environment_variable`), types with no\nengine adapter, and any `excluded_types` are dropped from the capture and\nrecorded in the snapshot's coverage report.\n", "tags": [ "Snapshots" ], @@ -173,9 +191,6 @@ }, "401": { "$ref": "#/components/responses/Unauthorized" - }, - "422": { - "$ref": "#/components/responses/UnprocessableEntity" } } } @@ -369,6 +384,89 @@ } } }, + "/v1/org-snapshot-schedule": { + "get": { + "operationId": "getOrgSnapshotSchedule", + "summary": "getOrgSnapshotSchedule", + "description": "Return the scheduled-snapshot enrollment config for the caller's org.\nReturns 404 when the org has not yet enrolled.\n", + "tags": [ + "ScheduledSnapshots" + ], + "responses": { + "200": { + "description": "Org snapshot schedule config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgSnapshotSchedule" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "operationId": "putOrgSnapshotSchedule", + "summary": "putOrgSnapshotSchedule", + "description": "Create or update the scheduled-snapshot enrollment config for the\ncaller's org (upsert). The cron expression and retention window are\nvalidated server-side; invalid values are rejected with 400.\n\nDefaults applied when a field is omitted on first create:\n - `cron_expression`: `cron(0 2 * * ? *)` (daily at 02:00)\n - `timezone`: `Europe/Berlin`\n - `retention`: `{ value: 90, unit: \"days\" }`\n - `enabled`: `true`\n\n**Note:** this call persists the row only. EventBridge schedule\nmaterialization is performed by a subsequent reconcile step (Task 6).\nThe row is the authoritative source of truth for the UI.\n", + "tags": [ + "ScheduledSnapshots" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutOrgSnapshotScheduleRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Stored config (after upsert)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgSnapshotSchedule" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "delete": { + "operationId": "deleteOrgSnapshotSchedule", + "summary": "deleteOrgSnapshotSchedule", + "description": "Remove the scheduled-snapshot enrollment for the caller's org.\nReturns 404 when no schedule exists.\nThe corresponding EventBridge schedule is removed by a reconcile\nstep (Task 6).\n", + "tags": [ + "ScheduledSnapshots" + ], + "responses": { + "204": { + "description": "Deleted" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, "/v1/snapshots:list-dependencies": { "post": { "operationId": "listDependencies", @@ -475,16 +573,6 @@ } } } - }, - "UnprocessableEntity": { - "description": "Unprocessable entity", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmptyInventoryError" - } - } - } } }, "schemas": { @@ -503,38 +591,6 @@ } } }, - "EmptyInventoryError": { - "type": "object", - "required": [ - "message", - "skipped_types" - ], - "description": "Returned (422) when the org inventory contains no capturable resources\nafter filtering out sensitive, unsupported, and excluded types. The\n`skipped_types` array explains why every type was dropped.\n", - "properties": { - "message": { - "type": "string", - "example": "No capturable resources in the org inventory" - }, - "skipped_types": { - "type": "array", - "items": { - "type": "object", - "required": [ - "type", - "reason" - ], - "properties": { - "type": { - "type": "string" - }, - "reason": { - "type": "string" - } - } - } - } - } - }, "ResourceRef": { "type": "object", "required": [ @@ -562,7 +618,8 @@ "properties": { "lineage_id": { "type": "string", - "description": "Cross-service correlation key — matches the lineage row id in\nblueprint-manifest-api's lineage table for `blueprint_install`\nsnapshots. Same as `target_id` for snapshots whose capture\ndoesn't distinguish source vs destination identifiers.\n" + "deprecated": true, + "description": "Deprecated alias of `target_id`. Always equals `target_id` (the\nimplementation never distinguished them). Use `target_id`.\n" }, "target_id": { "type": "string", @@ -727,17 +784,14 @@ }, "RestoreSnapshotRequest": { "type": "object", - "description": "Both flags default to `false`, which restores every captured resource —\nConfig Hub's manual-restore semantics. blueprint-manifest-api sets\nboth `true` when reverting a blueprint install so user edits and\ncross-blueprint contributions survive. Each flag is independent so a\ncaller can preserve edits without preserving co-ownership (or vice\nversa). Skipped resources surface under `Operation.skipped`.\n", + "description": "Apply a captured snapshot to its source org. snapshot-api applies the\nmanifest verbatim minus any target ids the caller pre-decided to skip.\nDrift detection (skip modified-since-install) is the caller's\nresponsibility — blueprint-manifest-api owns that logic for blueprint\nrestores; Config Hub's manual restore just omits the field.\n", "properties": { - "preserve_modified": { - "type": "boolean", - "default": false, - "description": "When `true`, skip captured resources whose live destination payload\nhas diverged from the install-time fingerprint stored on lineage.\nSurfaces under `Operation.skipped` with `reason: 'modified'`.\n" - }, - "preserve_co_owned": { - "type": "boolean", - "default": false, - "description": "When `true`, skip captured resources whose lineage row carries\nanother blueprint instance's id (co-ownership ≥2). Surfaces under\n`Operation.skipped` with `reason: 'co_owned'`.\n" + "exclude_target_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Target ids the caller has decided not to restore. snapshot-api\napplies the manifest minus these ids. Drops are silent — the\ncaller supplied the list and already knows.\n" } } }, @@ -891,57 +945,156 @@ "partial", "failed" ], - "description": "`partial` indicates the operation completed but skipped at least\none resource — see `skipped`. Only populated by restores triggered\nwith `mode: 'preserve_edits'`.\n" + "description": "`partial` indicates `engine.apply` reported a partial success\n(one or more resources failed individually) but the operation\nas a whole did not fail.\n" }, "error": { "type": "string" }, "triggered_by": { "$ref": "#/components/schemas/CallerIdentity" - }, - "skipped": { - "type": "array", - "description": "Per-resource skips, populated only for restores triggered with\n`mode: 'preserve_edits'`. Empty / absent for Config Hub's\ndefault overwrite-mode restores.\n", - "items": { - "$ref": "#/components/schemas/SkippedResource" - } } } }, - "SkippedResource": { + "CallerIdentity": { "type": "object", "required": [ - "lineage_id", - "reason" + "name" ], "properties": { - "lineage_id": { + "name": { "type": "string" }, - "reason": { + "user_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + }, + "RetentionConfig": { + "type": "object", + "required": [ + "value", + "unit" + ], + "description": "Flat retention window for a scheduled snapshot.\nConverted to a `ttl` epoch at capture time. Capped at ~24 months.\n", + "properties": { + "value": { + "type": "integer", + "minimum": 1, + "description": "Numeric quantity of retention (e.g. 90 for \"90 days\")." + }, + "unit": { "type": "string", "enum": [ - "modified", - "co_owned" - ], - "description": "- `modified` — current destination payload's fingerprint differs\n from the install-time fingerprint on the lineage row.\n- `co_owned` — lineage row has ≥2 distinct\n `blueprint_instance_ids`; restoring would unilaterally affect\n another blueprint instance's contribution.\n" + "days", + "weeks", + "months" + ] } } }, - "CallerIdentity": { + "PutOrgSnapshotScheduleRequest": { + "type": "object", + "description": "Body for `putOrgSnapshotSchedule`. All fields optional; unset fields\nreceive defaults on first create and are left unchanged on updates\n(except `updated_at`).\n", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Whether the schedule is active." + }, + "cron_expression": { + "type": "string", + "description": "6-field EventBridge cron expression, e.g. `cron(0 2 * * ? *)`.\nValidated server-side: minute + hour must be concrete single integers\n(no `*`, lists, ranges, or steps) to cap cadence at ≤ once/day.\nExactly one of day-of-month / day-of-week must be `?`.\n", + "example": "cron(0 2 * * ? *)" + }, + "timezone": { + "type": "string", + "description": "IANA timezone string passed to EventBridge `ScheduleExpressionTimezone`,\ne.g. `Europe/Berlin`. AWS handles DST natively.\n", + "example": "Europe/Berlin" + }, + "retention": { + "$ref": "#/components/schemas/RetentionConfig" + }, + "excluded_types": { + "type": "array", + "description": "Resource types to exclude from the scheduled capture, in addition\nto the always-excluded sensitive types (`access_token`,\n`environment_variable`).\n", + "items": { + "type": "string" + } + } + } + }, + "OrgSnapshotSchedule": { "type": "object", "required": [ - "name" + "org_id", + "enabled", + "cron_expression", + "timezone", + "retention", + "schedule_name", + "created_by", + "created_at", + "updated_at" ], + "description": "Enrollment record for a scheduled org snapshot. One row per org.\nThis table — not EventBridge — is the source of truth; the EventBridge\nschedule entry is the materialization of this row (reconciled on write\nby Task 6).\n", "properties": { - "name": { + "org_id": { "type": "string" }, - "user_id": { - "type": "string" + "enabled": { + "type": "boolean" }, - "token_id": { + "cron_expression": { + "type": "string", + "description": "Validated 6-field EventBridge cron. Default `cron(0 2 * * ? *)`.", + "example": "cron(0 2 * * ? *)" + }, + "timezone": { + "type": "string", + "description": "IANA timezone. Default `Europe/Berlin`." + }, + "retention": { + "$ref": "#/components/schemas/RetentionConfig" + }, + "excluded_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "schedule_name": { + "type": "string", + "description": "Name of the EventBridge Scheduler entry this row owns.\nSet at enrollment time as `org-snapshot-{orgId}`.\n" + }, + "last_started_at": { + "type": "string", + "format": "date-time" + }, + "last_completed_at": { + "type": "string", + "format": "date-time" + }, + "last_status": { + "type": "string", + "enum": [ + "completed", + "partial", + "failed" + ] + }, + "created_by": { "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" } } } diff --git a/packages/cli/definitions/targeting.json b/packages/cli/definitions/targeting.json index 8edf74caf..387725c82 100644 --- a/packages/cli/definitions/targeting.json +++ b/packages/cli/definitions/targeting.json @@ -37,7 +37,7 @@ "/v1/campaign/{campaign_id}/status": { "post": { "operationId": "changeCampaignStatus", - "summary": "Change the status of a campaign", + "summary": "changeCampaignStatus", "description": "Change the status of a campaign to a desired status.\n\nThe status can be one of the following: active, inactive.\n\nStatus transition is accompanied by side effects, e.g., automation execution.\n", "tags": [ "Campaign" @@ -69,7 +69,7 @@ "get": { "operationId": "getCampaignJobStatus", "description": "Get the status of a campaign's automation job", - "summary": "Get the status of a campaign's automation job", + "summary": "getCampaignJobStatus", "tags": [ "Campaign" ], @@ -99,7 +99,7 @@ "/v1/campaign/{campaign_id}/portals": { "get": { "operationId": "getCampaignPortals", - "summary": "Get portals usage info for a campaign", + "summary": "getCampaignPortals", "description": "Get the list of portals and its widgets where the campaign is used.\n", "tags": [ "Campaign" @@ -130,7 +130,7 @@ "/v1/campaign/{campaign_id}/automations:retrigger": { "post": { "operationId": "retriggerCampaignAutomations", - "summary": "Retrigger automations for campaign recipients", + "summary": "retriggerCampaignAutomations", "description": "Retrigger automation executions for specific campaign recipients that have failed.\n\nThis endpoint starts new automation executions for the specified recipients\nusing the campaign's associated automation flow. Only recipients with\nautomation_status 'failed' will be processed. Recipients with other statuses\n(success, pending, in_progress, cancelled) will be skipped to prevent\naccidentally retriggering successful or ongoing automations.\n", "tags": [ "Campaign Delivery" @@ -174,7 +174,7 @@ "/v1/campaign:setup": { "post": { "operationId": "setupCampaign", - "summary": "Set up a campaign with related entities and configurations", + "summary": "setupCampaign", "description": "Creates a `campaign` entity together with its related entities and configurations in a single call.\nUsed by the campaign wizard UI, but not restricted to it.\n", "tags": [ "Campaign" @@ -210,7 +210,7 @@ "/v1/campaign:match": { "post": { "operationId": "matchCampaigns", - "summary": "Match campaigns", + "summary": "matchCampaigns", "description": "Match campaigns based on target entities.\n\nThis endpoint returns the list of campaigns where the provided entities are part of the target.\n", "tags": [ "Campaign" @@ -243,10 +243,46 @@ } } }, + "/v1/campaign:discover": { + "post": { + "operationId": "discoverCampaigns", + "summary": "discoverCampaigns", + "description": "Given an entity, returns the Next Best Actions it should see on the Entity-UI channel.\n\nEnumerates the organization's **active** campaigns that carry a valid Entity-UI Next Best\nAction, live-matches each against the entity using the existing match engine, and returns\nthe matching NBAs priority-sorted (one per campaign).\n\nThis is a pure read: it writes nothing. An entity that matches no campaigns returns an\nempty list, not an error.\n", + "tags": [ + "Campaign" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DiscoverCampaignsParams" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/DiscoverCampaignsResponse" + }, + "400": { + "$ref": "#/components/responses/ClientErrorResponse" + }, + "500": { + "$ref": "#/components/responses/ServerErrorResponse" + } + } + } + }, "/v1/target:match": { "post": { "operationId": "matchTargets", - "summary": "Match targets", + "summary": "matchTargets", "description": "Find targets from the provided list that include the provide entities.\n", "tags": [ "Target" @@ -282,7 +318,7 @@ "/v1/target/queries": { "post": { "operationId": "getTargetQueries", - "summary": "Get target queries", + "summary": "getTargetQueries", "description": "Transform target filters into Lucene queries for the provided target IDs.\nReturns the transformed query string for each target along with any errors encountered.\n", "tags": [ "Target" @@ -318,7 +354,7 @@ "/v1/campaign/{campaign_id}/recipient": { "post": { "operationId": "createRecipient", - "summary": "Create a recipient associated with a campaign", + "summary": "createRecipient", "description": "Creates a new recipient associated with a campaign.", "tags": [ "Campaign Recipient" @@ -359,7 +395,7 @@ "/v1/campaign/{campaign_id}/recipient/{recipient_id}": { "patch": { "operationId": "updateRecipient", - "summary": "Update a recipient", + "summary": "updateRecipient", "description": "Updates a recipient's attributes.", "tags": [ "Campaign Recipient" @@ -403,7 +439,7 @@ "/v1/campaign/{campaign_id}/recipient/{recipient_id}/portal:status": { "patch": { "operationId": "updateRecipientPortalStatus", - "summary": "Update portal status for a campaign recipient", + "summary": "updateRecipientPortalStatus", "description": "Updates the portal status for a specific campaign recipient.\nThe portal_status_updated_at timestamp is automatically set when the status changes.\n\nStatus transition rules:\n- From 'sent': can change to 'seen', 'dismissed', or 'clicked'\n- From 'seen': can change to 'dismissed' or 'clicked'\n- From 'dismissed' or 'clicked': cannot be changed (final states)\n", "tags": [ "Campaign Recipient" @@ -450,10 +486,97 @@ } } }, + "/v1/campaign/{campaign_id}/recipient/{recipient_id}/entity_ui:status": { + "patch": { + "operationId": "updateRecipientEntityUiStatus", + "summary": "updateRecipientEntityUiStatus", + "description": "Records a Next Best Action interaction for a recipient on the Entity-UI channel.\n\nUnlike the portal channel, an NBA recipient is created lazily: the first `seen` creates\nthe recipient record (and requires `entity_schema`). `seen` is idempotent — re-viewing an\nNBA that is already seen/clicked/dismissed is a no-op success and never regresses the status.\n\nStatus transition rules:\n- `seen`: lazily creates the recipient; a no-op success if a status already exists\n- From `seen`: can change to `clicked` or `dismissed`\n- From `clicked`: can change to `dismissed`\n- From `dismissed`: cannot be changed via this operation — use `entity_ui:restore`\n\n`dismissed` and `clicked` require an existing recipient (404 otherwise, since an NBA is\nborn at `seen`) and reject invalid transitions (409).\n\nThe entity_ui_status_updated_at timestamp is automatically set when the status changes.\n", + "tags": [ + "Campaign Recipient" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/CampaignIdPathParam" + }, + { + "$ref": "#/components/parameters/RecipientIdPathParam" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEntityUiStatusRequest" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/RecipientResponse" + }, + "400": { + "$ref": "#/components/responses/ClientErrorResponse" + }, + "404": { + "$ref": "#/components/responses/ClientErrorResponse" + }, + "409": { + "$ref": "#/components/responses/ClientErrorResponse" + }, + "500": { + "$ref": "#/components/responses/ServerErrorResponse" + } + } + } + }, + "/v1/campaign/{campaign_id}/recipient/{recipient_id}/entity_ui:restore": { + "post": { + "operationId": "restoreRecipientEntityUiStatus", + "summary": "Undo a dismissal on the Entity-UI (Next Best Action) channel", + "description": "Reverses a dismissal so the Next Best Action becomes visible to the entity again. This is\nthe only way out of `dismissed` — `entity_ui:status` cannot leave that state.\n\nThe recipient's status is restored to whatever it was immediately before the dismissal\n(recorded in `entity_ui_status_before_dismiss`), so a `clicked` NBA that was dismissed\nreturns to `clicked` and does not lose its recorded click. Falls back to `seen` when no\nprevious status was recorded.\n\nScoped to the Entity-UI channel only: the recipient's portal and automation state is never\ntouched. Takes no request body — the recipient's current state fully determines the result.\n\nReturns 404 when the recipient has no Entity-UI status at all, and 409 when it has one but\nis not currently `dismissed` (there is nothing to undo).\n", + "tags": [ + "Campaign Recipient" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/CampaignIdPathParam" + }, + { + "$ref": "#/components/parameters/RecipientIdPathParam" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/RecipientResponse" + }, + "404": { + "$ref": "#/components/responses/ClientErrorResponse" + }, + "409": { + "$ref": "#/components/responses/ClientErrorResponse" + }, + "500": { + "$ref": "#/components/responses/ServerErrorResponse" + } + } + } + }, "/v1/campaign/{campaign_id}/recipients": { "get": { "operationId": "getRecipients", - "summary": "Get campaign recipients", + "summary": "getRecipients", "description": "Get a paginated list of recipients for a campaign.", "tags": [ "Campaign Recipient" @@ -495,9 +618,14 @@ { "name": "automation_status", "in": "query", - "description": "Filter by automation status", + "description": "Filter by automation status. Repeat the parameter to match multiple\nstatuses in one request\n(e.g. automation_status=failed&automation_status=cancelled).\n", + "style": "form", + "explode": true, "schema": { - "$ref": "#/components/schemas/AutomationStatus" + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationStatus" + } } }, { @@ -507,6 +635,14 @@ "schema": { "$ref": "#/components/schemas/PortalStatus" } + }, + { + "name": "email_status", + "in": "query", + "description": "Filter by email delivery status (e.g. bounced, delivered). The response total reflects the count for the filtered status.", + "schema": { + "$ref": "#/components/schemas/EmailStatus" + } } ], "responses": { @@ -521,6 +657,37 @@ } } } + }, + "/v1/campaign/{campaign_id}/email-stats": { + "get": { + "operationId": "getEmailStats", + "summary": "getEmailStats", + "description": "Aggregate email delivery counts for a campaign, for the KPI summary on the campaign UI.\nCounts cover the email (automation) channel only; `total_emailed` is the number of\nrecipients with a recorded email status. `delivered` is derivable as\n`total_emailed - bounced - complained - failed`.\n", + "tags": [ + "Campaign Recipient" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/CampaignIdPathParam" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/EmailStatsResponse" + }, + "400": { + "$ref": "#/components/responses/ClientErrorResponse" + }, + "500": { + "$ref": "#/components/responses/ServerErrorResponse" + } + } + } } }, "components": { @@ -575,6 +742,7 @@ "CAMPAIGN_NOT_FOUND", "CAMPAIGN_HAS_NO_TARGET", "CAMPAIGN_HAS_NO_DELIVERY_METHOD", + "INVALID_NEXT_BEST_ACTION", "CAMPAIGN_HAS_JOB_IN_PROGRESS", "CAMPAIGN_HAS_UNEXPECTED_STATUS", "JOB_TOKEN_MISSING", @@ -913,6 +1081,150 @@ ], "additionalProperties": false }, + "NextBestAction": { + "type": "object", + "description": "A Next Best Action configured on a campaign's Entity-UI channel.\nThis is the canonical NBA contract shared by discovery (this API), authoring, and rendering.\nNBA content is single-language in v1; text fields may contain `{{placeholders}}` resolved at render time.\n", + "properties": { + "category": { + "type": "string", + "maxLength": 30, + "description": "Light category label shown above the title. Free-form text." + }, + "icon": { + "type": "object", + "description": "Curated icon for the NBA.", + "properties": { + "name": { + "type": "string", + "description": "Icon name from \"@epilot360/icons\"." + }, + "color": { + "type": "string", + "description": "Optional icon color." + } + }, + "required": [ + "name" + ] + }, + "title": { + "type": "string", + "maxLength": 60, + "description": "Bold action title. Required. Supports `{{placeholders}}`." + }, + "body": { + "type": "string", + "maxLength": 140, + "description": "Optional description. Supports `{{placeholders}}` (incl. relative dates)." + }, + "priority": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "default": "medium", + "description": "Display priority. NBAs are shown highest-priority first." + }, + "is_dismissable": { + "type": "boolean", + "default": true, + "description": "Whether the agent can dismiss the NBA." + }, + "cta": { + "type": "object", + "description": "The NBA's single call-to-action.", + "properties": { + "type": { + "type": "string", + "enum": [ + "journey", + "workflow", + "flow" + ] + }, + "target": { + "type": "string", + "description": "Journey id, workflow definition id, or flow template id, depending on `type`." + }, + "context_params": { + "type": "array", + "description": "Journey context parameters (journey CTA only). Maps the journey's declared\ncontext parameters so the journey knows which entity it is about. Discovery\nreturns them verbatim; they are passed to the journey when it launches.\n", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ] + } + } + }, + "required": [ + "type", + "target" + ] + } + }, + "required": [ + "title", + "cta" + ] + }, + "DiscoverCampaignsParams": { + "type": "object", + "properties": { + "entity_id": { + "$ref": "#/components/schemas/BaseUUID" + }, + "entity_schema": { + "type": "string", + "description": "The schema slug of the entity (e.g. \"contact\" or \"account\")." + } + }, + "required": [ + "entity_id", + "entity_schema" + ], + "additionalProperties": false + }, + "DiscoverResult": { + "type": "object", + "description": "One discovered Next Best Action, plus this entity's interaction state for it.", + "properties": { + "campaign_id": { + "$ref": "#/components/schemas/BaseUUID" + }, + "nba": { + "$ref": "#/components/schemas/NextBestAction" + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityUiStatus" + } + ], + "description": "The recipient's current Entity-UI status for this campaign, present only when a\nrecipient record already exists (i.e. the entity has previously seen, clicked or\ndismissed this NBA). Absent when the entity has not yet interacted with it.\n\nIn `results` this is only ever `seen` or `clicked`; in `dismissed` it is always\n`dismissed`. Lets the client skip a redundant `seen` call for NBAs already seen.\n" + }, + "status_updated_at": { + "type": "string", + "format": "date-time", + "description": "When `status` was last written. Present whenever `status` is. Lets the client show how\nlong ago an NBA was dismissed.\n" + } + }, + "required": [ + "campaign_id", + "nba" + ] + }, "MatchTargetParams": { "type": "object", "properties": { @@ -1017,6 +1329,34 @@ "clicked" ] }, + "EntityUiStatus": { + "type": "string", + "description": "Lifecycle status of a Next Best Action on the Entity-UI channel. Unlike the portal\nchannel there is no `sent` state: an NBA recipient is born at `seen`, the moment the\naction is first rendered to an agent.\n", + "enum": [ + "seen", + "dismissed", + "clicked" + ] + }, + "EmailStatus": { + "type": "string", + "description": "Delivery status of the email a campaign's automation sends to a recipient. Set to `sent`\nonce the automation hands the email off, then updated asynchronously as SES notifications\narrive (`bounced` / `complained`; an SES Reject maps to `bounced`). `failed` is a send-time\nfailure (the automation execution could not send at all), distinct from an asynchronous\nbounce. `delivered` is reserved for when Delivery events are published.\n", + "enum": [ + "sent", + "delivered", + "bounced", + "complained", + "failed" + ] + }, + "EmailBounceType": { + "type": "string", + "description": "SES bounce classification: `permanent` (hard) or `transient` (soft).", + "enum": [ + "permanent", + "transient" + ] + }, "Recipient": { "type": "object", "properties": { @@ -1046,6 +1386,100 @@ "type": "object", "additionalProperties": true }, + "entity_ui_status": { + "$ref": "#/components/schemas/EntityUiStatus" + }, + "entity_ui_status_updated_at": { + "type": "string", + "format": "date-time" + }, + "entity_ui_status_before_dismiss": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityUiStatus" + } + ], + "description": "The Entity-UI status the recipient held immediately before it was dismissed, so\n`entity_ui:restore` can put it back without losing a recorded click.\n\nServer-managed — never send this from a client; it is ignored on write. Meaningful\n**only while** `entity_ui_status` is `dismissed`: a restore intentionally leaves the\nvalue behind rather than clearing it, so a stale value after a restore is expected and\nmust not be read.\n" + }, + "message_entity_id": { + "type": "string", + "description": "The message entity id of the email sent to this recipient by the campaign's automation\n(not the SES/provider message id), used to correlate SES delivery notifications back to\nthe recipient. Server-managed.\n" + }, + "email_status": { + "$ref": "#/components/schemas/EmailStatus" + }, + "email_status_updated_at": { + "type": "string", + "format": "date-time" + }, + "email_bounce_type": { + "$ref": "#/components/schemas/EmailBounceType" + }, + "email_bounce_subtype": { + "type": "string", + "description": "SES bounce sub-type (e.g. `General`, `NoEmail`, `MailboxFull`)." + }, + "email_complaint_type": { + "type": "string", + "description": "SES complaint feedback type (e.g. `abuse`, `fraud`), set for complaints." + }, + "email_bounce_reason": { + "type": "string", + "description": "Human-readable failure reason — the SES bounce `diagnosticCode`, the complaint\nfeedback type, or a send-time error message.\n" + }, + "email_send_error": { + "type": "object", + "additionalProperties": true, + "description": "The raw SES notification (or send error) kept verbatim for a detail view." + }, + "email_unsubscribed_at": { + "type": "string", + "format": "date-time", + "description": "When the contact unsubscribed from email marketing (opted out). Server-managed." + }, + "email_open_count": { + "type": "integer", + "description": "Number of SES opens recorded for this recipient (only where open tracking is on)." + }, + "email_first_opened_at": { + "type": "string", + "format": "date-time", + "description": "When the recipient first opened the email." + }, + "email_last_opened_at": { + "type": "string", + "format": "date-time", + "description": "When the recipient most recently opened the email." + }, + "email_click_count": { + "type": "integer", + "description": "Number of SES link clicks recorded for this recipient (only where click tracking is on)." + }, + "email_first_clicked_at": { + "type": "string", + "format": "date-time", + "description": "When the recipient first clicked a link." + }, + "email_clicked_links": { + "type": "array", + "description": "Raw click events (URL + timestamp); aggregate per-URL on the client.", + "items": { + "type": "object", + "required": [ + "url", + "clicked_at" + ], + "properties": { + "url": { + "type": "string" + }, + "clicked_at": { + "type": "string", + "format": "date-time" + } + } + } + }, "updated_at": { "type": "string", "format": "date-time" @@ -1211,6 +1645,22 @@ "status" ] }, + "UpdateEntityUiStatusRequest": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/EntityUiStatus" + }, + "entity_schema": { + "type": "string", + "description": "Schema slug of the recipient entity (e.g. \"contact\"). Required when recording the\nfirst `seen`, which lazily creates the recipient record; ignored on later transitions.\n" + } + }, + "required": [ + "status" + ], + "additionalProperties": false + }, "SetupCampaignRequest": { "description": "Discriminated by `type`. Each campaign variant has its own request shape;\nnew variants are added by introducing a new schema and extending the `oneOf` list.\n", "oneOf": [ @@ -1525,6 +1975,81 @@ } } }, + "EmailStatsResponse": { + "description": "Aggregate email delivery counts for a campaign.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "total_emailed", + "sent", + "delivered", + "bounced", + "bounced_hard", + "bounced_soft", + "complained", + "failed", + "unsubscribed", + "opened", + "clicked", + "total_opens", + "total_clicks" + ], + "properties": { + "total_emailed": { + "type": "integer", + "description": "Recipients with a recorded email status (the KPI denominator)." + }, + "sent": { + "type": "integer" + }, + "delivered": { + "type": "integer", + "description": "Recipients whose SES Delivery event was recorded (needs Delivery publishing enabled)." + }, + "bounced": { + "type": "integer" + }, + "bounced_hard": { + "type": "integer", + "description": "Bounces classified permanent (bounced_hard + bounced_soft = bounced)." + }, + "bounced_soft": { + "type": "integer", + "description": "Bounces classified transient." + }, + "complained": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "unsubscribed": { + "type": "integer", + "description": "Recipients who opted out of email marketing." + }, + "opened": { + "type": "integer", + "description": "Unique recipients with at least one open (only where open tracking is on)." + }, + "clicked": { + "type": "integer", + "description": "Unique recipients with at least one link click (only where click tracking is on)." + }, + "total_opens": { + "type": "integer", + "description": "Total opens across all recipients." + }, + "total_clicks": { + "type": "integer", + "description": "Total link clicks across all recipients." + } + } + } + } + } + }, "MatchCampaignsResponse": { "description": "List of campaigns where the target entities match the given entities.", "content": { @@ -1556,6 +2081,41 @@ } } }, + "DiscoverCampaignsResponse": { + "description": "The Next Best Actions the entity should see on the Entity-UI channel, priority-sorted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "hits": { + "type": "number", + "description": "Number of matching NBAs. Counts `results` only — dismissed ones are excluded." + }, + "results": { + "type": "array", + "description": "Matching NBAs, sorted by priority (desc); one entry per campaign.", + "items": { + "$ref": "#/components/schemas/DiscoverResult" + } + }, + "dismissed": { + "type": "array", + "description": "NBAs this entity has dismissed that would otherwise be in `results` — same shape,\nalso priority-sorted. Returned so the client can show the agent what it hid and\noffer to restore it, without a second round-trip.\n\nOnly currently-relevant dismissals appear: each one is matched against its\ncampaign's target exactly like a visible NBA, so a dismissal is dropped once the\ncampaign ends or the entity stops matching. Capped, so this is not a complete\ndismissal history.\n", + "items": { + "$ref": "#/components/schemas/DiscoverResult" + } + } + }, + "required": [ + "hits", + "results", + "dismissed" + ] + } + } + } + }, "MatchTargetsResponse": { "description": "List of targets where the given entities are found.", "content": { diff --git a/packages/cli/definitions/user.json b/packages/cli/definitions/user.json index 8021221d9..4d7fc33be 100644 --- a/packages/cli/definitions/user.json +++ b/packages/cli/definitions/user.json @@ -1046,6 +1046,117 @@ } } }, + "/v2/users/public/resetPassword": { + "post": { + "operationId": "resetPassword", + "summary": "resetPassword", + "description": "Set a new password using a reset token from the password reset email.\nThe token is single-use and time-limited.\n", + "security": [], + "x-rate-limit": { + "limit": 10 + }, + "tags": [ + "User V2" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "token", + "password" + ], + "properties": { + "email": { + "type": "string", + "description": "Email address of the account", + "example": "test@example.com" + }, + "token": { + "type": "string", + "description": "Reset token from the password reset email" + }, + "password": { + "type": "string", + "description": "The new password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password reset successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "example": true + } + } + } + } + } + }, + "400": { + "description": "Invalid or expired reset link, or password does not meet the requirements", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string", + "description": "Discriminator distinguishing token errors from policy errors", + "enum": [ + "INVALID_RESET_LINK", + "PASSWORD_POLICY" + ] + }, + "message": { + "type": "string", + "example": "Password does not meet the requirements" + }, + "violations": { + "type": "array", + "description": "Itemized password-policy violation codes (only present when error is PASSWORD_POLICY)", + "items": { + "type": "string", + "enum": [ + "too_short", + "missing_lowercase", + "missing_uppercase", + "missing_number", + "missing_symbol" + ] + }, + "example": [ + "too_short", + "missing_uppercase" + ] + } + } + } + } + } + } + } + } + }, "/v2/users/public/checkToken": { "get": { "operationId": "checkInviteToken", @@ -1998,6 +2109,19 @@ "format": "date-time", "readOnly": true }, + "password_reset_token_hash": { + "description": "Internal: sha256 hash of the active password-reset token. Server-set, never returned in responses.", + "type": "string", + "nullable": true, + "readOnly": true + }, + "password_reset_token_expires_at": { + "description": "Internal: expiry for the active password-reset token (ISO 8601). Server-set, never returned in responses.", + "type": "string", + "nullable": true, + "format": "date-time", + "readOnly": true + }, "department": { "type": "string", "description": "User's department", @@ -2130,6 +2254,17 @@ "deleted_opportunity": true } }, + "in_app_notification_setting": { + "type": "object", + "additionalProperties": true, + "description": "Per-notification-type in-app delivery preferences (notification type key -> enabled). Written by the my-account notification settings UI and honored by svc-notification-api at delivery time. Absent or partial keys fall back to each notification type's configured default. Mirrors email_notification_setting for the in-app channel.", + "example": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + } + }, "properties": { "type": "array", "items": { @@ -2149,6 +2284,17 @@ "value" ] } + }, + "tags": { + "type": "array", + "description": "User tags/labels assigned for classification (e.g. label slugs)", + "nullable": true, + "items": { + "type": "string" + }, + "example": [ + "non-billable" + ] } } }, @@ -2297,6 +2443,10 @@ "passkey_enabled": { "type": "boolean", "description": "Whether passkey login is enabled for this organization" + }, + "passkeys_registered": { + "type": "boolean", + "description": "Whether the user has at least one passkey registered. Lets the login\nUI offer passkey authentication as an alternative to an MFA code.\nDiscloses nothing that :beginPasskeyAuthentication doesn't already\nreveal for a known email address.\n" } } }, diff --git a/packages/cli/definitions/validation-rules.json b/packages/cli/definitions/validation-rules.json index 95f49bb76..001a1b928 100644 --- a/packages/cli/definitions/validation-rules.json +++ b/packages/cli/definitions/validation-rules.json @@ -2,12 +2,13 @@ "openapi": "3.0.3", "info": { "title": "Validation Rules API", - "version": "1.0.0" + "version": "1.0.0", + "description": "The Validation Rules API manages reusable input validation rules for epilot journeys and entity attributes.\n\nValidation rules define constraints for user input (e.g. regex patterns, numeric ranges, character counts) that can be applied to journey blocks or entity schema attributes.\n\nKey capabilities:\n- Define validation rules using regex patterns, character patterns, or numeric constraints\n- Apply rules to journey blocks or entity schema attributes via the `used_by` association\n- Manage the lifecycle of rules (create, read, update, delete)\n- Compose complex validation logic using AND/OR/NOT condition combinators\n\n## Rule schema versions\n\nThe `rule` property holds one of two shapes, identified by the document's `_schema_version`:\n- `v1`: a json-rules-engine condition tree (regex/pattern/numeric) with static comparison values only.\n- `v2`: a declarative rule with predefined comparison operators whose comparison values may be static,\n dynamic (a path into runtime context declared via `contexts`,\n e.g. `contract.installment_amount`) or relative dates. v2 shapes carry an `input_type` property.\nConverting a rule between schema versions is not supported.\n" }, "tags": [ { "name": "Validation Rules", - "description": "Endpoints for managing validation rules" + "description": "CRUD endpoints for managing validation rules within an organization.\nAll endpoints require an epilot bearer token and operate within the authenticated organization's scope.\nRules are identified by a unique `ruleId` and can be referenced by journey blocks or entity attributes via the `used_by` field.\n" } ], "security": [ @@ -19,8 +20,8 @@ "/v1/validation-rules": { "get": { "operationId": "getValidationRules", - "summary": "Get all validation rules by organization Id", - "description": "Gets all validation rules by organization Id", + "summary": "getValidationRules", + "description": "Returns all validation rules belonging to the authenticated user's organization.\n\nResults are returned as a flat list. Use this endpoint to list available rules when configuring journeys or entity schemas.\n", "tags": [ "Validation Rules" ], @@ -36,6 +37,32 @@ "application/json": { "schema": { "$ref": "#/components/schemas/GetValidationRulesResponse" + }, + "example": { + "results": [ + { + "_id": "rule-abc123", + "_organization_id": "728224", + "_schema_version": "1.0", + "title": "German postal code", + "created_at": "2024-01-10T08:00:00.000Z", + "updated_at": "2024-01-10T08:00:00.000Z", + "created_by": "user-1", + "updated_by": "user-1", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$" + } + ] + } + } + } + ] } } } @@ -56,6 +83,38 @@ } } }, + "401": { + "description": "Unauthorized - Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Forbidden" + } + } + } + } + } + }, "500": { "description": "Internal Server Error", "content": { @@ -76,8 +135,8 @@ }, "post": { "operationId": "createValidationRule", - "summary": "Create Validation Rule", - "description": "Creates a new validation rule", + "summary": "createValidationRule", + "description": "Creates a new validation rule for the authenticated organization.", "tags": [ "Validation Rules" ], @@ -87,22 +146,134 @@ } ], "requestBody": { + "description": "Validation rule to create", + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateValidationRuleRequest" + }, + "example": { + "title": "German postal code", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$", + "params": { + "errorMessage": "Must be a 5-digit German postal code" + } + } + ] + } + } } } - }, - "description": "Payload" + } }, "responses": { "201": { - "description": "Success", + "description": "Validation rule created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationRule" + }, + "example": { + "_id": "rule-abc123", + "_organization_id": "728224", + "_schema_version": "1.0", + "title": "German postal code", + "created_at": "2024-01-10T08:00:00.000Z", + "updated_at": "2024-01-10T08:00:00.000Z", + "created_by": "user-1", + "updated_by": "user-1", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad Request - Invalid request body. Invalid external comparison values additionally carry a\n`code` (`external_value_not_found` when the app is not installed or the hook / result does not\nexist, `external_value_incompatible` when the result type does not match the compared kind).\n", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Invalid request body" + }, + "code": { + "type": "string", + "enum": [ + "external_value_not_found", + "external_value_incompatible" + ] + } + } + } + } + } + }, + "401": { + "description": "Unauthorized - Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Forbidden" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unknown API Error" + } + } } } } @@ -113,8 +284,8 @@ "/v1/validation-rules/{ruleId}": { "get": { "operationId": "getValidationRuleById", - "summary": "Get validation rule by ID", - "description": "Retrieves a specific validation rule by its ID", + "summary": "getValidationRuleById", + "description": "Retrieves a specific validation rule by its unique ID.", "tags": [ "Validation Rules" ], @@ -131,16 +302,71 @@ "schema": { "type": "string" }, - "description": "The unique identifier of the validation rule to retrieve." + "description": "The unique identifier of the validation rule to retrieve.", + "example": "rule-abc123" } ], "responses": { "200": { - "description": "Success", + "description": "Validation rule retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationRule" + }, + "example": { + "_id": "rule-abc123", + "_organization_id": "728224", + "_schema_version": "1.0", + "title": "German postal code", + "created_at": "2024-01-10T08:00:00.000Z", + "updated_at": "2024-01-10T08:00:00.000Z", + "created_by": "user-1", + "updated_by": "user-1", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$" + } + ] + } + } + } + } + } + }, + "401": { + "description": "Unauthorized - Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Forbidden" + } + } } } } @@ -181,8 +407,8 @@ }, "patch": { "operationId": "updateValidationRule", - "summary": "Update Validation Rule (partial update)", - "description": "Updates an existing validation rule partially by ID", + "summary": "updateValidationRule", + "description": "Partially updates an existing validation rule by ID. Only the fields provided in the request body are updated.", "tags": [ "Validation Rules" ], @@ -199,7 +425,8 @@ "schema": { "type": "string" }, - "description": "The unique identifier of the validation rule to update." + "description": "The unique identifier of the validation rule to update.", + "example": "rule-abc123" } ], "requestBody": { @@ -207,6 +434,24 @@ "application/json": { "schema": { "$ref": "#/components/schemas/UpdateValidationRuleRequest" + }, + "example": { + "title": "Updated German postal code", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$", + "params": { + "errorMessage": "Must be a valid 5-digit German postal code" + } + } + ] + } + } } } }, @@ -214,17 +459,39 @@ }, "responses": { "200": { - "description": "Success", + "description": "Validation rule updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationRule" + }, + "example": { + "_id": "rule-abc123", + "_organization_id": "728224", + "_schema_version": "1.0", + "title": "Updated German postal code", + "created_at": "2024-01-10T08:00:00.000Z", + "updated_at": "2024-01-20T11:00:00.000Z", + "created_by": "user-1", + "updated_by": "user-2", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$" + } + ] + } + } } } } }, "400": { - "description": "Bad Request - Invalid request body", + "description": "Bad Request - Invalid request body. Invalid external comparison values additionally carry a\n`code` (`external_value_not_found` when the app is not installed or the hook / result does not\nexist, `external_value_incompatible` when the result type does not match the compared kind).\n", "content": { "application/json": { "schema": { @@ -233,6 +500,61 @@ "message": { "type": "string", "example": "Invalid request body" + }, + "code": { + "type": "string", + "enum": [ + "external_value_not_found", + "external_value_incompatible" + ] + } + } + } + } + } + }, + "401": { + "description": "Unauthorized - Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Forbidden" + } + } + } + } + } + }, + "404": { + "description": "Validation rule not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Validation rule not found" } } } @@ -259,8 +581,8 @@ }, "delete": { "operationId": "deleteValidationRule", - "summary": "Delete Validation Rule", - "description": "Deletes a validation rule by ID", + "summary": "deleteValidationRule", + "description": "Permanently deletes a validation rule by ID. Any journeys or entity attributes referencing this rule should be updated before deletion.", "tags": [ "Validation Rules" ], @@ -277,13 +599,62 @@ "schema": { "type": "string" }, - "description": "The unique identifier of the validation rule to delete." + "description": "The unique identifier of the validation rule to delete.", + "example": "rule-abc123" } ], "responses": { "204": { "description": "Validation rule deleted successfully" }, + "401": { + "description": "Unauthorized - Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Forbidden" + } + } + } + } + } + }, + "404": { + "description": "Validation rule not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Validation rule not found" + } + } + } + } + } + }, "500": { "description": "Internal Server Error", "content": { @@ -306,8 +677,8 @@ "/v1/validation-rules/{ruleId}/used-by": { "post": { "operationId": "addUsedByReference", - "summary": "Add a reference to the usedBy array", - "description": "Adds a single reference to the usedBy array of a validation rule", + "summary": "addUsedByReference", + "description": "Adds a single `used_by` reference to an existing validation rule.\n\nUse this endpoint when attaching a validation rule to a journey block or entity attribute to track where the rule is applied.\n", "tags": [ "Validation Rules" ], @@ -324,7 +695,8 @@ "schema": { "type": "string" }, - "description": "The unique identifier of the validation rule to update." + "description": "The unique identifier of the validation rule to update.", + "example": "rule-abc123" } ], "requestBody": { @@ -332,10 +704,14 @@ "application/json": { "schema": { "$ref": "#/components/schemas/UsedBy" + }, + "example": { + "type": "journey", + "source_id": "journey-xyz789" } } }, - "description": "The usedBy reference to add" + "description": "The used_by reference to add" }, "responses": { "200": { @@ -344,6 +720,34 @@ "application/json": { "schema": { "$ref": "#/components/schemas/ValidationRule" + }, + "example": { + "_id": "rule-abc123", + "_organization_id": "728224", + "_schema_version": "1.0", + "title": "German postal code", + "created_at": "2024-01-10T08:00:00.000Z", + "updated_at": "2024-01-20T11:00:00.000Z", + "created_by": "user-1", + "updated_by": "user-1", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$" + } + ] + } + }, + "used_by": [ + { + "type": "journey", + "source_id": "journey-xyz789" + } + ] } } } @@ -364,6 +768,38 @@ } } }, + "401": { + "description": "Unauthorized - Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Forbidden" + } + } + } + } + } + }, "404": { "description": "Validation rule not found", "content": { @@ -400,8 +836,8 @@ }, "delete": { "operationId": "removeUsedByReference", - "summary": "Remove a reference from the usedBy array", - "description": "Removes a specific reference from the usedBy array of a validation rule", + "summary": "removeUsedByReference", + "description": "Removes a specific `used_by` reference from an existing validation rule.\n\nUse this endpoint when detaching a validation rule from a journey block or entity attribute.\n", "tags": [ "Validation Rules" ], @@ -418,7 +854,8 @@ "schema": { "type": "string" }, - "description": "The unique identifier of the validation rule to update." + "description": "The unique identifier of the validation rule to update.", + "example": "rule-abc123" } ], "requestBody": { @@ -426,10 +863,14 @@ "application/json": { "schema": { "$ref": "#/components/schemas/UsedBy" + }, + "example": { + "type": "journey", + "source_id": "journey-xyz789" } } }, - "description": "The usedBy reference to remove" + "description": "The used_by reference to remove" }, "responses": { "200": { @@ -438,6 +879,29 @@ "application/json": { "schema": { "$ref": "#/components/schemas/ValidationRule" + }, + "example": { + "_id": "rule-abc123", + "_organization_id": "728224", + "_schema_version": "1.0", + "title": "German postal code", + "created_at": "2024-01-10T08:00:00.000Z", + "updated_at": "2024-01-20T11:00:00.000Z", + "created_by": "user-1", + "updated_by": "user-1", + "rule": { + "type": "regex", + "conditions": { + "all": [ + { + "fact": "inputValue", + "operator": "regexMatch", + "value": "^[0-9]{5}$" + } + ] + } + }, + "used_by": [] } } } @@ -458,6 +922,38 @@ } } }, + "401": { + "description": "Unauthorized - Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Forbidden" + } + } + } + } + } + }, "404": { "description": "Validation rule not found", "content": { @@ -512,9 +1008,11 @@ "schemas": { "GetValidationRulesResponse": { "type": "object", + "description": "Response envelope for listing all validation rules within an organization.", "properties": { "results": { "type": "array", + "description": "Flat list of all validation rules belonging to the authenticated organization.", "items": { "$ref": "#/components/schemas/ValidationRule" } @@ -572,8 +1070,18 @@ }, { "$ref": "#/components/schemas/NumericRuleType" + }, + { + "$ref": "#/components/schemas/ComparisonRuleType" } ] + }, + "contexts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContextRequirement" + }, + "description": "Declares the dynamic context a v2 rule needs at evaluation time.\n`context` condition values reference these sources by using the schema slug\nas the first segment of their `path`. Only applicable to v2 rules.\n" } } } @@ -633,6 +1141,7 @@ }, "UsedBy": { "type": "object", + "description": "Describes where and how a validation rule is applied. Used to track associations between rules and the journeys or entity schemas that reference them.", "required": [ "type" ], @@ -643,18 +1152,24 @@ "journey", "entity" ], - "description": "The context in which the rule is used (e.g., journey or entity)." + "description": "The context in which the rule is used - either a journey block or an entity schema attribute.", + "example": "journey" }, "schema_slug": { "type": "string", - "description": "Slug of the schema using this rule for entities." + "description": "Slug of the entity schema using this rule. Only applicable when `type` is `entity`.", + "example": "contact" }, "source_id": { "type": "string", - "description": "Source identifier for the usage context." + "description": "Unique identifier of the source (e.g. journey ID or entity attribute key) that references this rule.", + "example": "journey-xyz789" } }, - "description": "Describes where and how a validation rule is applied." + "example": { + "type": "journey", + "source_id": "journey-xyz789" + } }, "RegexRuleType": { "description": "Validation rule that uses a regular expression to validate input.", @@ -1480,6 +1995,464 @@ } } ] + }, + "ComparisonRuleType": { + "type": "object", + "description": "Declarative validation rule (schema version v2). Supports predefined comparison operators\nover number, date and text inputs, with static, dynamic (context path), relative-date and\nexternal (app-provided) comparison values.\n", + "required": [ + "input_type", + "conditions" + ], + "additionalProperties": false, + "properties": { + "input_type": { + "type": "string", + "enum": [ + "number", + "date", + "text" + ], + "description": "The kind of input value the rule validates. Determines which operators are allowed." + }, + "conditions": { + "type": "array", + "description": "The comparisons the input value must satisfy. All blocking conditions must pass\nfor the input to be valid; `allow_failure` conditions are advisory and excluded\nfrom the verdict. Must contain at least one condition (enforced at write time).\n", + "items": { + "$ref": "#/components/schemas/Condition" + } + } + } + }, + "Condition": { + "type": "object", + "description": "A single comparison the input value must satisfy.", + "required": [ + "id", + "operator", + "value", + "error_message" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "description": "Stable identifier of the condition within the rule, used for editing and error reporting." + }, + "operator": { + "$ref": "#/components/schemas/Operator" + }, + "value": { + "$ref": "#/components/schemas/ConditionValue" + }, + "error_message": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "description": "Message shown to the end user when this condition fails." + }, + "applies_when": { + "$ref": "#/components/schemas/AppliesWhen" + }, + "allow_failure": { + "type": "boolean", + "default": false, + "description": "When true, the condition is advisory: it is always evaluated and reported\nwhen it fails, but it never takes part in the validity verdict.\nThe input is valid when every blocking (non-advisory) condition passes.\nA rule whose conditions are all advisory is always valid (warnings only).\n" + } + } + }, + "Operator": { + "type": "string", + "description": "Predefined comparison operator. Compatibility (enforced at write time):\n- number: equal, notEqual, greaterThan, greaterThanInclusive, lessThan, lessThanInclusive, between, regexMatch,\n maxDigits, maxDecimals\n- date: dateBefore, dateOnOrBefore, dateAfter, dateOnOrAfter, dateBetween, notInFuture, notInPast, regexMatch\n- text: equal, notEqual, contains, doesNotContain, startsWith, endsWith, regexMatch, lengthBetween,\n greaterThan, greaterThanInclusive, lessThan, lessThanInclusive, between, maxDigits, maxDecimals\nRange operators (between, dateBetween, lengthBetween) require a `range` value;\nunary operators (notInFuture, notInPast) require a `none` value; all others require a scalar value.\nregexMatch validates the raw input string's format and always takes a static string pattern.\nNumeric comparison operators on text rules parse the input as a number at evaluation time\n(free-text fields often hold numbers); unparsable input fails the condition.\nmaxDigits limits how many digits the written input may contain in total (grouping\nseparators, sign and the decimal separator are not counted); maxDecimals limits how many\ndigits may follow the decimal separator. Both take a non-negative integer comparison value\nand, like the other numeric operators, are also allowed on text rules.\n", + "enum": [ + "equal", + "notEqual", + "greaterThan", + "greaterThanInclusive", + "lessThan", + "lessThanInclusive", + "between", + "dateBefore", + "dateOnOrBefore", + "dateAfter", + "dateOnOrAfter", + "dateBetween", + "notInFuture", + "notInPast", + "contains", + "doesNotContain", + "startsWith", + "endsWith", + "regexMatch", + "lengthBetween", + "maxDigits", + "maxDecimals" + ] + }, + "ConditionValue": { + "description": "The comparison value of a condition - a scalar, a range of scalars, or nothing (unary operators).", + "oneOf": [ + { + "$ref": "#/components/schemas/StaticValue" + }, + { + "$ref": "#/components/schemas/ContextValue" + }, + { + "$ref": "#/components/schemas/RelativeDateValue" + }, + { + "$ref": "#/components/schemas/EnvironmentValue" + }, + { + "$ref": "#/components/schemas/ExternalValue" + }, + { + "$ref": "#/components/schemas/RangeValue" + }, + { + "$ref": "#/components/schemas/NoValue" + } + ] + }, + "ScalarValue": { + "description": "A single comparison value - static, resolved from context, a relative date, an organisation environment variable, or an external value.", + "oneOf": [ + { + "$ref": "#/components/schemas/StaticValue" + }, + { + "$ref": "#/components/schemas/ContextValue" + }, + { + "$ref": "#/components/schemas/RelativeDateValue" + }, + { + "$ref": "#/components/schemas/EnvironmentValue" + }, + { + "$ref": "#/components/schemas/ExternalValue" + } + ] + }, + "EnvironmentValue": { + "type": "object", + "description": "A comparison value resolved at evaluation time from an organisation environment variable\n(environments-api). The rule stores the key, never the value, so one change to the variable\nreaches every rule that references it and a blueprint install never overwrites the\norganisation's own value.\n\nOnly browser-safe variable types are allowed: `Number` for numeric comparisons, `Text` for\ntext comparisons and for dates (ISO 8601 string), `Boolean` for `applies_when` values.\n`String` and `SecretString` variables are rejected at write time. Write-time validation also\nchecks that the variable exists in the organisation; a variable that exists without a value\n(for example seeded by a blueprint install) is accepted and resolves as unavailable until set.\n", + "required": [ + "source", + "key" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "environment" + ] + }, + "key": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_.\\-]{0,127}$", + "description": "Environment variable key, e.g. `abschlag.max`." + }, + "adjust": { + "$ref": "#/components/schemas/ValueAdjustment" + } + } + }, + "ExternalValue": { + "type": "object", + "description": "A comparison value produced at evaluation time by an External Values hook of an installed\napp (component type `EXTERNAL_VALUES`). The rule stores the reference only; the value is\nresolved server-side by the external-values-api, which executes the hook's HTTP call with\nthe app's credentials and returns the typed result identified by `result_id`.\n\nWrite-time validation checks that the app is installed in the organisation, that the hook\nand result exist, and that the result's type is compatible with the compared kind\n(`number` results for numeric comparisons, `text` for text, `date` for date). External values\nare resolved only for authenticated consumers (epilot 360 users and portal end customers);\nin public journeys the dependent conditions are skipped.\n", + "required": [ + "source", + "app_id", + "hook_id", + "result_id" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "external" + ] + }, + "app_id": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "ID of the installed app that provides the External Values component." + }, + "hook_id": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "ID of the hook within the app's External Values component." + }, + "result_id": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "ID of the result entry within the hook whose value is compared." + }, + "adjust": { + "$ref": "#/components/schemas/ValueAdjustment" + } + } + }, + "StaticValue": { + "type": "object", + "description": "A fixed comparison value.", + "required": [ + "source", + "data" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "static" + ] + }, + "data": { + "description": "The literal value. Numbers for numeric comparisons, ISO 8601 date strings for date comparisons, strings for text comparisons.", + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "boolean" + } + ] + } + } + }, + "ContextValue": { + "type": "object", + "description": "A dynamic comparison value resolved from runtime context, e.g. `contract.installment_amount`\nor `previous_reading.value`. The first path segment must match the `name` of a declared\ncontext requirement.\n", + "required": [ + "source", + "path" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "context" + ] + }, + "path": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z0-9_]+)*$", + "description": "Dot-separated path into the resolved context." + }, + "adjust": { + "$ref": "#/components/schemas/ValueAdjustment" + } + } + }, + "ValueAdjustment": { + "type": "object", + "description": "Adjusts a context-resolved numeric value before comparison, e.g. \"context value plus 10 percent\".\nUsed to express tolerance bands such as \"at most 10% above the current instalment amount\".\n", + "required": [ + "type", + "value", + "direction" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "percent", + "absolute" + ] + }, + "value": { + "description": "The adjustment amount - a fixed number, or a context path resolving to a number\n(e.g. a per-contract percentage limit). Contextual adjustment values must not\ncarry a nested adjustment.\n", + "oneOf": [ + { + "type": "number", + "minimum": 0 + }, + { + "$ref": "#/components/schemas/ContextValue" + }, + { + "$ref": "#/components/schemas/EnvironmentValue" + } + ] + }, + "direction": { + "type": "string", + "enum": [ + "increase", + "decrease" + ] + }, + "rounding": { + "type": "string", + "enum": [ + "up", + "down" + ], + "description": "Rounds the adjusted result to a whole number - `up` (ceiling) or `down` (floor).\nOmitted means no rounding. Used for bounds like \"round up(current × 0.9) to whole euros\".\n" + } + } + }, + "RelativeDateValue": { + "type": "object", + "description": "A date relative to the evaluation moment, e.g. \"today minus 30 days\". Only valid for date rules.", + "required": [ + "source", + "offset", + "unit" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "relative_date" + ] + }, + "offset": { + "type": "integer", + "minimum": -36500, + "maximum": 36500, + "description": "Offset from the anchor. Negative values are in the past, positive in the future, 0 is the anchor itself." + }, + "unit": { + "type": "string", + "enum": [ + "days", + "months", + "years" + ] + }, + "anchor": { + "type": "string", + "enum": [ + "today" + ], + "default": "today" + } + } + }, + "RangeValue": { + "type": "object", + "description": "A lower and upper bound for range operators (between, dateBetween, lengthBetween). Bounds are inclusive.", + "required": [ + "source", + "min", + "max" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "range" + ] + }, + "min": { + "$ref": "#/components/schemas/ScalarValue" + }, + "max": { + "$ref": "#/components/schemas/ScalarValue" + } + } + }, + "NoValue": { + "type": "object", + "description": "No comparison value - used by unary operators such as notInFuture / notInPast.", + "required": [ + "source" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "none" + ] + } + } + }, + "AppliesWhen": { + "type": "object", + "description": "Optional precondition on a condition: the condition only takes part in the\nvalidation when this comparison over context holds. Examples: apply the\ndual-tariff reference only when `contract.htnt` is not empty, or run a\nplausibility check only when a context value reaches a threshold.\n", + "required": [ + "path", + "operator" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z0-9_]+)*$", + "description": "Dot-separated context path whose first segment must match a declared context schema." + }, + "operator": { + "type": "string", + "enum": [ + "equal", + "notEqual", + "greaterThan", + "greaterThanInclusive", + "lessThan", + "lessThanInclusive", + "isEmpty", + "isNotEmpty" + ], + "description": "Comparison applied to the resolved context value. `isEmpty` / `isNotEmpty`\ntake no `value`; all other operators require one.\n" + }, + "value": { + "description": "Comparison value for binary operators: a static number, string or boolean, or an\norganisation environment variable (`Number`, `Text` or `Boolean`) so a precondition\ncan act as a per-organisation switch or threshold.\n", + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "boolean" + }, + { + "$ref": "#/components/schemas/EnvironmentValue" + } + ] + } + } + }, + "ContextRequirement": { + "type": "object", + "description": "An entity context source the rule needs at evaluation time, referenced by `context`\nvalue paths via the schema slug as their first segment (e.g. `contract.installment_amount`).\nHow the source is resolved (which entity instance) is decided by the consuming surface,\nnot by the rule. Meter reading comparisons use the meter/meter_counter entity schemas\n(e.g. `meter_counter.current_consumption` for the previous reading value).\n", + "required": [ + "schema" + ], + "additionalProperties": false, + "properties": { + "schema": { + "type": "string", + "minLength": 1, + "description": "Entity schema slug.", + "example": "contract" + } + } } } }, diff --git a/packages/cli/definitions/webhooks.json b/packages/cli/definitions/webhooks.json index d06c862c5..a9940a109 100644 --- a/packages/cli/definitions/webhooks.json +++ b/packages/cli/definitions/webhooks.json @@ -763,6 +763,17 @@ "description": "Event id" } ], + "requestBody": { + "description": "Optional replay options", + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplayRequest" + } + } + } + }, "responses": { "204": { "description": "Success - replay event" @@ -777,6 +788,16 @@ } } }, + "422": { + "description": "Re-apply requested but the original (pre-transform) input could not be reconstructed (e.g. dangling catalog reference past retention).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, "500": { "description": "Other errors", "content": { @@ -1073,14 +1094,15 @@ }, "WebhookCondition": { "type": "object", - "description": "A condition that must be met for the webhook to fire.", + "description": "A condition that must be met for the webhook to fire. A condition is EITHER a classic field + operation comparison OR a single jsonata_expression evaluated against the event payload — the two forms are mutually exclusive.", "properties": { "field": { "type": "string", - "description": "Dot-notation path to the field in the event payload (e.g. \"entity.status\", \"entity.line_items\")" + "description": "Dot-notation path to the field in the event payload (e.g. \"entity.status\", \"entity.line_items\"). Supports array indices in either spelling, which resolve identically: \"meter_readings.1.file_id\" or \"meter_readings[1].file_id\"." }, "operation": { "type": "string", + "description": "Comparison operator. The length_* operations compare the item count of the field value (an array yields its length, a missing or null field yields 0, and any single non-array value yields 1) against values[0], which must be a non-negative integer.", "enum": [ "equals", "not_equals", @@ -1095,7 +1117,13 @@ "greater_than_or_equals", "less_than_or_equals", "is_empty", - "is_not_empty" + "is_not_empty", + "length_equals", + "length_not_equals", + "length_greater_than", + "length_less_than", + "length_greater_than_or_equals", + "length_less_than_or_equals" ] }, "values": { @@ -1126,12 +1154,13 @@ "type": "boolean", "description": "When true, evaluates conditions per-item in repeatable array fields", "default": false + }, + "jsonata_expression": { + "type": "string", + "maxLength": 15000, + "description": "JSONata expression evaluated against the event payload; the condition passes when the result is truthy per JSONata $boolean semantics. Mutually exclusive with field/operation." } - }, - "required": [ - "field", - "operation" - ] + } }, "WebhookConditionGroup": { "type": "object", @@ -1477,7 +1506,8 @@ ], "properties": { "status_code": { - "type": "string" + "type": "string", + "description": "The HTTP status returned by the customer endpoint (or the upstream gateway). ABSENT for epilot-internal errors (code INTERNAL_ERROR / INTERNAL_RESPONSE_TOO_LARGE), which are not customer HTTP outcomes; PRESENT only for real HTTP/upstream results. When the response was delivered but too large to record, a recovered delivered status may still be present." }, "message": { "type": "string" @@ -1486,7 +1516,8 @@ "type": "object" }, "code": { - "type": "string" + "type": "string", + "description": "Machine-readable classification of the outcome. Known values: OAuthTokenExchangeError (OAuth token exchange / upstream gateway failure), DataLimitExceeded (legacy oversized-response marker), INTERNAL_RESPONSE_TOO_LARGE (request delivered but the response was too large for epilot to record), INTERNAL_ERROR (epilot-internal processing failure — not a customer HTTP outcome)." }, "status": { "type": "string", @@ -1657,7 +1688,8 @@ "type": "object", "properties": { "status_code": { - "type": "integer" + "type": "integer", + "description": "The HTTP status returned by the customer endpoint (or the upstream gateway). ABSENT for epilot-internal errors (code INTERNAL_ERROR / INTERNAL_RESPONSE_TOO_LARGE), which are not customer HTTP outcomes; PRESENT only for real HTTP/upstream results. When the response was delivered but too large to record, a recovered delivered status may still be present." }, "message": { "type": "string" @@ -1666,7 +1698,8 @@ "type": "object" }, "code": { - "type": "string" + "type": "string", + "description": "Machine-readable classification of the outcome. Known values: OAuthTokenExchangeError (OAuth token exchange / upstream gateway failure), DataLimitExceeded (legacy oversized-response marker), INTERNAL_RESPONSE_TOO_LARGE (request delivered but the response was too large for epilot to record), INTERNAL_ERROR (epilot-internal processing failure — not a customer HTTP outcome)." } } }, @@ -1694,9 +1727,26 @@ "type": "string", "description": "stringified payload of the webhook request" }, + "original_payload": { + "type": "string", + "description": "Stringified pre-transform original payload — the raw input captured before any JSONata transform was applied. Returned by getEventById for non-catalog events that have a locally stored original source (inline or claim-checked to S3, hydrated here). Omitted when no original source exists or when the payload would exceed the inline response size limit. Catalog-backed events (event_-prefixed) do not carry a local original — their pre-transform payload is sourced from the event catalog instead." + }, "retry_attempt": { "type": "integer", "description": "Number of automatic delivery retries that preceded this terminal outcome. 0 means the event was delivered (or finally failed) on the first attempt." + }, + "can_reapply_transform": { + "type": "boolean", + "description": "Whether this event can be replayed with the JSONata transform re-applied. True only when the webhook config has a non-empty jsonataExpression AND a usable original source exists (a catalog reference or a locally stored original payload). Computed without a synchronous catalog round-trip." + }, + "can_reapply_transform_reason": { + "type": "string", + "enum": [ + "available", + "no_transform_configured", + "no_original_source" + ], + "description": "Reason explaining the can_reapply_transform value." } }, "required": [ @@ -1766,6 +1816,16 @@ "message" ] }, + "ReplayRequest": { + "type": "object", + "properties": { + "reapply_transform": { + "type": "boolean", + "default": false, + "description": "When true, reconstruct the original (pre-transform) event input and re-run the full delivery pipeline (file-loading + JSONata). When false or omitted, resend the previously delivered (post-transform) payload verbatim (backward-compatible default)." + } + } + }, "BatchReplayRequest": { "type": "object", "properties": { @@ -1781,6 +1841,11 @@ "2f1b7cf8-ff55-4359-966f-e56f39a52c94", "48c984bf-466b-470b-b743-d07cea168243" ] + }, + "reapply_transform": { + "type": "boolean", + "default": false, + "description": "When true, reconstruct the original (pre-transform) input for each event and re-run the full delivery pipeline (file-loading + JSONata). When false or omitted, resend the previously delivered (post-transform) payload verbatim (backward-compatible default)." } }, "required": [ diff --git a/packages/cli/definitions/workflow-definition.json b/packages/cli/definitions/workflow-definition.json index e5db147d5..d1a16b39d 100644 --- a/packages/cli/definitions/workflow-definition.json +++ b/packages/cli/definitions/workflow-definition.json @@ -3,13 +3,27 @@ "info": { "title": "Workflows Definitions", "version": "1.1.0", - "description": "Service for Workflow Definitions for different processes inside of an Organization\n" + "description": "The Workflows Definitions API enables you to create, manage, and configure reusable workflow templates\nwithin your organization. Workflow definitions serve as blueprints that define the structure and behavior\nof business processes, which can then be instantiated as workflow executions.\n\n## Core Concepts\n\n### Workflow Definition (V1)\nA workflow definition is a template that describes a business process. It consists of:\n- **Sections**: Logical groupings (phases) that organize related steps together\n- **Steps**: Individual tasks or actions that need to be completed within a section\n- **Closing Reasons**: Predefined reasons that can be selected when closing/completing a workflow\n\n### Flow Template (V2)\nThe modern workflow model that provides advanced capabilities:\n- **Phases**: Named stages that group related tasks and track progress through the workflow\n- **Tasks**: Individual units of work that can be manual, automated, AI-powered, or decision points\n- **Edges**: Connections between tasks that define the flow sequence and support conditional branching\n- **Triggers**: Define how a workflow is started (manual, automation, journey submission)\n\n### Task Types (V2)\n- **MANUAL**: Tasks assigned to users that require human action to complete\n- **AUTOMATION**: Tasks that execute automated actions when reached\n- **DECISION**: Conditional branching points that evaluate conditions to determine the next path\n- **AI_AGENT**: Tasks that invoke AI agents to perform intelligent actions\n\n## API Versions\n- **V1 endpoints** (`/v1/workflows/...`): Legacy linear workflow model with sections and steps\n- **V2 endpoints** (`/v2/flows/...`): Modern graph-based flow model with phases, tasks, and edges\n" }, "servers": [ { "url": "https://workflows-definition.sls.epilot.io" } ], + "tags": [ + { + "name": "Workflows", + "description": "Manage V1 workflow definitions with sections and steps. These endpoints support the legacy\nlinear workflow model where definitions contain a sequential flow of sections and steps.\n" + }, + { + "name": "Flows V2", + "description": "Manage V2 flow templates with phases, tasks, and edges. The modern workflow model supports\nadvanced features including conditional branching, multiple trigger types, automation tasks,\nAI agent tasks, and flexible task dependencies.\n" + }, + { + "name": "Closing Reason", + "description": "Manage closing reasons that can be associated with workflows. Closing reasons provide\npredefined options for users to select when closing or completing a workflow execution,\nenabling better tracking and reporting of workflow outcomes.\n" + } + ], "security": [ { "BearerAuth": [] @@ -40,7 +54,7 @@ } }, "500": { - "description": "Other errors", + "description": "Internal server error", "content": { "application/json": { "schema": { @@ -56,7 +70,7 @@ "get": { "operationId": "getDefinitions", "summary": "getDefinitions", - "description": "Retrieve all Workflow Definitions from an Organization", + "description": "Retrieve all V1 workflow definitions belonging to the authenticated organization.", "tags": [ "Workflows" ], @@ -135,7 +149,7 @@ } }, "500": { - "description": "Other errors", + "description": "Internal server error", "content": { "application/json": { "schema": { @@ -149,7 +163,7 @@ "post": { "operationId": "createDefinition", "summary": "createDefinition", - "description": "Create a Workflow Definition.", + "description": "Create a new V1 workflow definition. The definition consists of sections and steps\nthat define the structure of the workflow. Once created, the definition can be used\nto start workflow executions.\n", "tags": [ "Workflows" ], @@ -387,17 +401,89 @@ "post": { "operationId": "createFlowTemplate", "summary": "createFlowTemplate", - "description": "Create a new Flow Template.", + "description": "Create a new Flow Template (V2 workflow definition).\n\nA flow template defines the structure of a workflow including its phases, tasks,\nedges (connections between tasks), and trigger configuration. Once created, the\ntemplate can be used to start workflow executions.\n", "tags": [ "Flows V2" ], + "parameters": [ + { + "$ref": "#/components/parameters/EnforceLimitsParam" + } + ], "requestBody": { - "description": "Flow Template payload", + "description": "Flow Template payload containing the workflow structure.\n\nRequired fields:\n- `name`: Display name for the workflow\n- `tasks`: Array of task definitions (manual, automation, decision, or AI agent tasks)\n- `edges`: Array of connections defining the flow between tasks\n", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateFlowTemplate" + }, + "example": { + "name": "Customer Onboarding", + "description": "Standard customer onboarding workflow with verification steps", + "version": "v3", + "enabled": true, + "trigger": { + "type": "manual", + "entity_schema": "contact" + }, + "phases": [ + { + "id": "phase_1", + "name": "Verification" + }, + { + "id": "phase_2", + "name": "Setup" + } + ], + "tasks": [ + { + "id": "task_1", + "name": "Verify customer identity", + "task_type": "MANUAL", + "phase_id": "phase_1", + "assigned_to": [ + "user_123" + ] + }, + { + "id": "task_2", + "name": "Send welcome email", + "task_type": "AUTOMATION", + "phase_id": "phase_2", + "automation_config": { + "flow_id": "automation_456" + }, + "trigger_mode": "automatic" + }, + { + "id": "task_3", + "name": "Schedule follow-up", + "task_type": "MANUAL", + "phase_id": "phase_2" + } + ], + "edges": [ + { + "id": "edge_1", + "from_id": "task_1", + "to_id": "task_2" + }, + { + "id": "edge_2", + "from_id": "task_2", + "to_id": "task_3" + } + ], + "closing_reasons": [ + { + "id": "reason_completed" + }, + { + "id": "reason_cancelled" + } + ] } } } @@ -512,7 +598,7 @@ "get": { "operationId": "getFlowTemplate", "summary": "getFlowTemplate", - "description": "Get specific FLow template for a customer", + "description": "Retrieve a specific flow template by its unique identifier.", "tags": [ "Flows V2" ], @@ -523,7 +609,8 @@ "schema": { "$ref": "#/components/schemas/FlowTemplateId" }, - "required": true + "required": true, + "description": "Unique identifier of the flow template to retrieve." } ], "responses": { @@ -593,11 +680,15 @@ "schema": { "$ref": "#/components/schemas/FlowTemplateId" }, - "required": true + "required": true, + "description": "Unique identifier of the flow template to update." + }, + { + "$ref": "#/components/parameters/EnforceLimitsParam" } ], "requestBody": { - "description": "Flow Template payload", + "description": "Updated flow template payload containing the full workflow definition.", "required": true, "content": { "application/json": { @@ -619,7 +710,7 @@ } }, "400": { - "description": "Validation Errors", + "description": "Validation errors - request body contains invalid data", "content": { "application/json": { "schema": { @@ -629,7 +720,7 @@ } }, "401": { - "description": "Authentication Errors", + "description": "Authentication failed - invalid or missing Bearer token", "content": { "application/json": { "schema": { @@ -638,6 +729,16 @@ } } }, + "404": { + "description": "Flow template not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DefinitionNotFoundResp" + } + } + } + }, "409": { "description": "Conflict - the workflow was modified by another user since it was last loaded", "content": { @@ -649,7 +750,7 @@ } }, "500": { - "description": "Other errors", + "description": "Internal server error", "content": { "application/json": { "schema": { @@ -675,7 +776,7 @@ "$ref": "#/components/schemas/FlowTemplateId" }, "required": true, - "description": "Id of the flow template to de deleted." + "description": "Unique identifier of the flow template to be deleted." } ], "responses": { @@ -693,7 +794,24 @@ } }, "404": { - "description": "No definition found" + "description": "Flow template not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DefinitionNotFoundResp" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } } } } @@ -702,7 +820,7 @@ "post": { "operationId": "duplicateFlowTemplate", "summary": "duplicateFlowTemplate", - "description": "Duplicate a Flow Template from an existing workflow.", + "description": "Create a copy of an existing flow template. The duplicated template will have a new\nunique identifier and can be modified independently of the original. This is useful\nfor creating variations of existing workflows without starting from scratch.\n", "tags": [ "Flows V2" ], @@ -713,7 +831,8 @@ "schema": { "$ref": "#/components/schemas/FlowTemplateId" }, - "required": true + "required": true, + "description": "Unique identifier of the flow template to duplicate." } ], "responses": { @@ -728,7 +847,7 @@ } }, "400": { - "description": "Validation Errors", + "description": "Validation errors - source template contains invalid data", "content": { "application/json": { "schema": { @@ -738,7 +857,7 @@ } }, "401": { - "description": "Authentication Errors", + "description": "Authentication failed - invalid or missing Bearer token", "content": { "application/json": { "schema": { @@ -747,8 +866,18 @@ } } }, + "404": { + "description": "Source flow template not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DefinitionNotFoundResp" + } + } + } + }, "500": { - "description": "Other errors", + "description": "Internal server error", "content": { "application/json": { "schema": { @@ -1106,7 +1235,7 @@ "type": "string" }, "required": true, - "description": "Id of the definition to de deleted.", + "description": "Unique identifier of the workflow definition to be deleted.", "example": "CustomerRequest" } ], @@ -1143,7 +1272,7 @@ "type": "boolean" }, "required": false, - "description": "Filter Closing Reasons by status like active inactiv", + "description": "When set to true, includes inactive closing reasons in the response. By default, only active closing reasons are returned.", "example": true } ], @@ -1588,6 +1717,18 @@ } }, "components": { + "parameters": { + "EnforceLimitsParam": { + "in": "query", + "name": "enforce_limits", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "description": "When true, size/count limit violations (max incoming edges per task, max branches, max entity-sync rules, ...) are enforced as 400 errors. Used by the flow-builder UI for interactive authoring. On update, pre-existing violations are tolerated as long as the request does not worsen them. When false or absent (default for API/system callers such as blueprints, configuration hub and snapshots), limit violations are accepted and returned as `limit_warnings`. Structural errors are always enforced regardless of this flag." + } + }, "securitySchemes": { "BearerAuth": { "type": "http", @@ -1598,6 +1739,7 @@ "schemas": { "FlowTemplateBase": { "type": "object", + "description": "Base schema for V2 flow templates. A flow template defines the structure of a workflow\nincluding phases, tasks, edges, triggers, and other configuration. Flow templates serve\nas blueprints that can be instantiated as flow executions.\n", "required": [ "name", "tasks", @@ -1605,16 +1747,22 @@ ], "properties": { "id": { - "type": "string" + "type": "string", + "description": "Unique identifier for the flow template, generated by the system." }, "org_id": { - "type": "string" + "type": "string", + "description": "Organization identifier that owns this flow template." }, "name": { - "type": "string" + "type": "string", + "description": "Display name for the workflow, shown to users in the UI.", + "minLength": 3 }, "description": { - "type": "string" + "type": "string", + "description": "Optional detailed description of the workflow's purpose and behavior.", + "maxLength": 5000 }, "trigger": { "$ref": "#/components/schemas/Trigger" @@ -1715,6 +1863,64 @@ "items": { "type": "string" } + }, + "linear": { + "type": "boolean", + "description": "When true, this is a linear flow: task enablement is computed at runtime from the graph — a task is enabled only when all of its direct predecessor tasks in its branch are done. This fully overrides any explicit per-task requirements." + }, + "limit_warnings": { + "type": "array", + "description": "Non-blocking warnings for configuration limits the flow exceeds. Returned on read (get-flow) and on create/update responses. Size/count limits are soft: a flow exceeding them is still accepted and stored, and the violations are reported here so the UI can surface an informational banner. The flow is still fully editable and usable.", + "items": { + "$ref": "#/components/schemas/FlowLimitWarning" + } + } + } + }, + "FlowLimitWarning": { + "type": "object", + "description": "A configuration limit that the flow currently exceeds. Returned by the get-flow endpoint and by create/update responses so the UI can show a non-blocking banner. Size/count limit violations never cause a 400 — only structural errors do. The flow remains fully usable; users are encouraged to bring values within the limits.", + "required": [ + "i18nKey", + "message", + "max", + "path" + ], + "properties": { + "i18nKey": { + "type": "string", + "description": "i18n key identifying the exceeded limit (e.g. errors.task_max_incoming_edges)." + }, + "message": { + "type": "string", + "description": "Human-readable description of the exceeded limit." + }, + "max": { + "type": "number", + "description": "The configured maximum for this limit." + }, + "current": { + "type": "number", + "description": "The actual value the flow currently has for this limit (e.g. the actual number of incoming connections)." + }, + "node_id": { + "type": "string", + "description": "ID of the task/node the limit applies to, when applicable." + }, + "task_name": { + "type": "string" + }, + "branch_name": { + "type": "string" + }, + "param_name": { + "type": "string", + "description": "Name of the AI-agent parameter the limit applies to, when applicable." + }, + "path": { + "type": "array", + "items": {}, + "description": "JSON path within the flow to the offending value." } } }, @@ -2159,7 +2365,8 @@ ] }, "duration": { - "type": "number" + "type": "number", + "minimum": 1 }, "unit": { "$ref": "#/components/schemas/TimeUnit" @@ -2182,7 +2389,8 @@ ] }, "duration": { - "type": "number" + "type": "number", + "minimum": 1 }, "unit": { "$ref": "#/components/schemas/TimeUnit" @@ -2273,8 +2481,7 @@ "type": "integer", "description": "Maximum number of iterations for the loop branch", "default": 3, - "minimum": 1, - "maximum": 100 + "minimum": 1 } }, "required": [ @@ -2473,7 +2680,6 @@ "branch_name": { "type": "string", "description": "The name of the branch", - "maxLength": 255, "example": "Branch 1" }, "logical_operator": { @@ -2597,6 +2803,22 @@ "deleted" ] }, + "attributes": { + "type": "array", + "maxItems": 10, + "items": { + "type": "string" + }, + "description": "Multi-attribute mode. When present and length > 1, the statement is\nevaluated against every listed attribute and combined via\n`attributes_match`. All listed attributes must share the same\n`attribute_type`. Mutually exclusive with `attribute_sub_field`,\n`date_offset`, and `attribute_operation`. When absent or length === 1,\nthe legacy `attribute` field is used.\n" + }, + "attributes_match": { + "type": "string", + "enum": [ + "any", + "all" + ], + "description": "Inner connector across `attributes`. `any` (default) means at least\none attribute must satisfy the operator; `all` means every attribute\nmust satisfy it. Ignored when `attributes` is absent or has length < 2.\n" + }, "attribute_sub_field": { "type": "string", "description": "For complex attribute types, specifies which sub-field to extract (e.g., \"address\", \"name\", \"email_type\")" @@ -2681,7 +2903,8 @@ "hours", "days", "weeks", - "months" + "months", + "years" ] }, "EnableRequirement": { @@ -2710,15 +2933,19 @@ }, "WorkflowDefinition": { "type": "object", + "description": "V1 workflow definition schema. Defines a linear workflow structure with sections\nand steps. Sections group related steps together, and steps represent individual\ntasks that need to be completed. This is the legacy workflow model; for new\nimplementations, consider using V2 flow templates instead.\n", "properties": { "id": { - "type": "string" + "type": "string", + "description": "Unique identifier for the workflow definition, generated by the system." }, "name": { - "type": "string" + "type": "string", + "description": "Display name for the workflow, shown to users in the UI." }, "description": { - "type": "string" + "type": "string", + "description": "Optional detailed description of the workflow's purpose and behavior." }, "creationTime": { "type": "string", @@ -3029,7 +3256,8 @@ "type": "boolean" }, "value": { - "type": "string" + "type": "string", + "maxLength": 10000 } } }, @@ -3045,18 +3273,24 @@ } }, "DefinitionNotFoundResp": { - "description": "Definition could be not found", + "type": "object", + "description": "Error response returned when a workflow definition or flow template is not found.", "properties": { "message": { - "type": "string" + "type": "string", + "description": "Human-readable message indicating the resource was not found.", + "example": "Definition with id 'abc123' not found" } } }, "ClosingReasonNotFoundResp": { - "description": "Closing reason could be not found", + "type": "object", + "description": "Error response returned when a closing reason is not found.", "properties": { "message": { - "type": "string" + "type": "string", + "description": "Human-readable message indicating the closing reason was not found.", + "example": "Closing reason with id 'xyz789' not found" } } }, @@ -3143,9 +3377,12 @@ }, "ErrorResp": { "type": "object", + "description": "Standard error response returned when an API request fails.", "properties": { "message": { - "type": "string" + "type": "string", + "description": "Human-readable error message describing what went wrong.", + "example": "Invalid request body: name is required" } } }, @@ -3296,11 +3533,12 @@ ] }, "DynamicDueDate": { - "description": "set a Duedate for a step then a specific", + "description": "Configuration for calculating a due date dynamically based on workflow events.\nThe due date is computed by adding a duration (numberOfUnits + timePeriod) to\na reference point defined by actionTypeCondition (e.g., when workflow started,\nwhen a step closed, or when a phase finished).\n", "type": "object", "properties": { "numberOfUnits": { - "type": "number" + "type": "number", + "description": "Number of time units to add to the reference point." }, "timePeriod": { "$ref": "#/components/schemas/TimeUnit" diff --git a/packages/cli/definitions/workflow.json b/packages/cli/definitions/workflow.json index 59531c481..edfb93a18 100644 --- a/packages/cli/definitions/workflow.json +++ b/packages/cli/definitions/workflow.json @@ -3,7 +3,7 @@ "info": { "title": "Workflows Executions", "version": "1.0.0", - "description": "Service for Workflow Executions which covers executions of processes defined in an Organization\n" + "description": "## Overview\nThe Workflows Executions API manages the runtime instances of workflow processes within an organization.\nWhile **Workflow Definitions** (managed by the Workflows Definitions API) serve as reusable templates\nthat define the structure, phases, and tasks of a process, **Workflow Executions** are the active\ninstances created from those definitions.\n\n## Key Concepts\n\n### Definitions vs Executions\n- **Definition (Template)**: A blueprint defining workflow structure, phases, tasks, conditions, and automation rules\n- **Execution (Instance)**: A running instance of a definition, tracking actual progress, assignees, and status\n\n### Execution Lifecycle\n1. **STARTED**: Execution is active and tasks can be worked on\n2. **DONE**: All required tasks are completed\n3. **CLOSED**: Execution is terminated (completed successfully or cancelled with closing reasons)\n\n### Task Types\n- **Manual Tasks**: Require human action to complete\n- **Automation Tasks**: Execute configured automations automatically\n- **Decision Tasks**: Evaluate conditions to determine the next path in the workflow\n- **AI Agent Tasks**: Execute AI-powered agents for intelligent task processing\n\n### Contexts\nExecutions are linked to entity contexts (e.g., contacts, opportunities) that provide the data\ncontext for the workflow and allow tracking which entities a workflow operates on.\n\n## API Versions\n- **V1 (`/v1/workflows/`)**: Legacy linear phase/section/step model (deprecated for new integrations)\n- **V2 (`/v2/flows/`)**: Current graph-based model with advanced features like conditional branching,\n loops, and scheduling. **Recommended for all new integrations.**\n" }, "servers": [ { @@ -13,16 +13,19 @@ "tags": [ { "name": "Workflow Executions", - "description": "Interact with workflow executions - start / close / retrieve one or all / search / delete" + "description": "Manage V1 workflow executions (legacy linear model). Operations include starting new executions\nfrom definitions, retrieving execution details, updating execution status and assignees,\nand deleting executions. Use the Flows V2 endpoints for new integrations.\n" }, { - "name": "Workflow Steps" + "name": "Workflow Steps", + "description": "Manage individual steps within V1 workflow executions (legacy). Steps represent discrete tasks\nthat can be assigned to users, have due dates, and track completion status. Use the Flows V2\ntask endpoints for new integrations.\n" }, { - "name": "Closing Reasons" + "name": "Closing Reasons", + "description": "Retrieve closing reasons configured for workflow executions. When a workflow is closed/cancelled,\nusers can select from predefined closing reasons to document why the workflow ended. Closing\nreasons are snapshots from the definition at execution creation time.\n" }, { - "name": "Flows V2" + "name": "Flows V2", + "description": "**Recommended for new integrations.** Manage V2 flow executions using the graph-based execution model.\nThis API version supports advanced features including:\n- Conditional branching with decision tasks\n- Automation tasks with configurable triggers\n- AI agent tasks for intelligent processing\n- Task scheduling (immediate, delayed, or relative to events)\n- Loop iterations for repeatable task sequences\n- Phase-based organization with progress tracking\n" } ], "security": [ @@ -90,24 +93,61 @@ "post": { "operationId": "createExecution", "summary": "createExecution", - "description": "Create a Workflow Execution. Start a new workflow execution, based on a workflow definition (template).", + "description": "Creates a new V1 Workflow Execution from a workflow definition (template).\n\n**Note:** This is the legacy V1 API. For new integrations, use `POST /v2/flows/executions` instead.\n\nThe workflow definition specifies the structure (sections and steps) of the workflow.\nWhen created, the execution instantiates all steps and begins tracking progress.\n", "tags": [ "Workflow Executions" ], "requestBody": { - "description": "Workflow Execution payload", + "description": "Workflow execution creation payload with definition ID and entity contexts", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WorkflowExecutionCreateReq" + }, + "examples": { + "basicCreate": { + "summary": "Create with single context", + "description": "Create an execution linked to an opportunity entity", + "value": { + "workflowId": "j3f23fh23uif98", + "trigger": "MANUAL", + "contexts": [ + { + "id": "3fa3fa86-0907-4642-a57e-0fe30a19874d", + "title": "New Opportunity", + "schema": "opportunity" + } + ] + } + }, + "automatedTrigger": { + "summary": "Create from automation", + "description": "Create an execution triggered automatically by an automation rule", + "value": { + "workflowId": "j3f23fh23uif98", + "trigger": "AUTOMATIC", + "contexts": [ + { + "id": "3fa3fa86-0907-4642-a57e-0fe30a19874d", + "title": "John Doe", + "schema": "contact" + }, + { + "id": "3a6d42fa-5070-4723-b90f-41ead4303e33", + "title": "Sales Opportunity", + "schema": "opportunity" + } + ] + } + } } } } }, "responses": { "201": { - "description": "Success - if the execution is created successfully", + "description": "Execution created successfully. Returns the complete workflow execution\nincluding all sections, steps, and initial status.\n", "content": { "application/json": { "schema": { @@ -117,31 +157,40 @@ } }, "400": { - "description": "Validation Errors", + "description": "Validation error. Common causes include:\n- Missing required workflowId\n- Invalid workflowId (definition not found)\n- Invalid context entity references\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "workflowId is required" } } } }, "401": { - "description": "Authentication Errors", + "description": "Authentication failed. The Bearer token is missing, expired, or invalid.\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Unauthorized" } } } }, "500": { - "description": "Other errors", + "description": "Internal server error. An unexpected error occurred while processing the request.\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Internal server error" } } } @@ -153,7 +202,7 @@ "get": { "operationId": "getExecution", "summary": "getExecution", - "description": "Get a full workflow execution, included steps information, by execution id.", + "description": "Retrieves a complete V1 workflow execution by ID, including all steps information.\n\n**Note:** This is the legacy V1 API. For new integrations, use `GET /v2/flows/executions/{execution_id}` instead.\n", "tags": [ "Workflow Executions" ], @@ -356,7 +405,7 @@ "patch": { "operationId": "updateStep", "summary": "updateStep", - "description": "Patches various changes to a workflow execution step.", + "description": "Updates a workflow execution step with new values for status, assignees, due date, position, and more.\n\n**Note:** This is the legacy V1 API. For new integrations, use `PATCH /v2/flows/executions/{execution_id}/tasks/{task_id}` instead.\n\n**Common use cases:**\n- Mark a step as completed or skipped\n- Assign or reassign users to a step\n- Update step due dates (static or dynamic)\n- Reorder steps within a section\n", "tags": [ "Workflow Steps" ], @@ -368,7 +417,7 @@ "type": "string" }, "required": true, - "description": "Id of the execution", + "description": "Unique identifier of the workflow execution", "example": "wd56125gah" }, { @@ -378,12 +427,12 @@ "type": "string" }, "required": true, - "description": "Short uuid (length 6) to identify the Workflow Execution Step.", + "description": "Short unique identifier (typically 6 characters) of the step within the execution", "example": "7hj28a" } ], "requestBody": { - "description": "Workflow Execution Step payload", + "description": "Step update payload with fields to modify", "required": true, "content": { "application/json": { @@ -391,24 +440,57 @@ "$ref": "#/components/schemas/UpdateStepReq" }, "examples": { - "dynamicDueDate": { + "markCompleted": { + "summary": "Mark step as completed", + "description": "Complete a step, advancing the workflow", + "value": { + "status": "COMPLETED" + } + }, + "markInProgress": { + "summary": "Mark step as in progress", + "description": "Indicate that work has started on this step", + "value": { + "status": "IN_PROGRESS" + } + }, + "assignUsers": { + "summary": "Assign users to step", + "description": "Assign one or more users to the step", "value": { - "stepId": "string", "assignedTo": [ - "10010730" - ], + "10010730", + "10010731" + ] + } + }, + "setStaticDueDate": { + "summary": "Set static due date", + "description": "Set an explicit due date for the step", + "value": { + "dueDate": "2024-06-15T17:00:00.000Z" + } + }, + "setDynamicDueDate": { + "summary": "Set dynamic due date", + "description": "Configure due date relative to workflow start or another step", + "value": { "dynamicDueDate": { "numberOfUnits": 2, "timePeriod": "weeks", "actionTypeCondition": "STEP_CLOSED", - "stepId": "optional" - }, - "name": "Static Duedate", + "stepId": "abc123" + } + } + }, + "reorderStep": { + "summary": "Reorder step position", + "description": "Move a step to a different position within its section", + "value": { "position": { - "index": 0 - }, - "sectionId": "string", - "status": "string" + "index": 2, + "sectionId": "section_abc123" + } } } } @@ -417,7 +499,7 @@ }, "responses": { "200": { - "description": "Success - if the step is updated successfully", + "description": "Step updated successfully. Returns the updated step object with all current values.\n", "content": { "application/json": { "schema": { @@ -681,24 +763,80 @@ "post": { "operationId": "startFlowExecution", "summary": "startFlowExecution", - "description": "Starts a new Flow Execution based on a flow template.", + "description": "Starts a new Flow Execution based on a flow template (definition).\n\nThe flow template defines the structure of the workflow including phases, tasks, edges (transitions),\nand automation configurations. When started, the execution creates runtime instances of all tasks\nand begins processing from the initial task(s).\n\n**Required fields:**\n- `flow_template_id`: The ID of the flow template to instantiate\n- `contexts`: At least one entity context to link the execution to\n\n**Optional fields:**\n- `trigger`: Specifies how the execution was triggered (manual or automatic)\n- `purposes`: Filter which phases/tasks are included based on taxonomy purposes\n", "tags": [ "Flows V2" ], "requestBody": { - "description": "Flow Execution payload", + "description": "Flow Execution payload containing the template ID and entity contexts", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StartFlowReq" + }, + "examples": { + "basicStart": { + "summary": "Start with single entity context", + "description": "Start a flow execution linked to an opportunity entity", + "value": { + "flow_template_id": "tpl_abc123def456", + "contexts": [ + { + "entity_id": "3fa3fa86-0907-4642-a57e-0fe30a19874d", + "entity_schema": "opportunity", + "is_primary": true + } + ] + } + }, + "multipleContexts": { + "summary": "Start with multiple entity contexts", + "description": "Start a flow execution linked to both a contact and an opportunity", + "value": { + "flow_template_id": "tpl_abc123def456", + "trigger": { + "type": "MANUAL" + }, + "contexts": [ + { + "entity_id": "3fa3fa86-0907-4642-a57e-0fe30a19874d", + "entity_schema": "contact", + "is_primary": true + }, + { + "entity_id": "7bc8de90-1234-5678-90ab-cdef12345678", + "entity_schema": "opportunity", + "is_primary": false + } + ] + } + }, + "withPurposes": { + "summary": "Start with purpose filtering", + "description": "Start a flow execution with specific purposes to filter applicable phases", + "value": { + "flow_template_id": "tpl_abc123def456", + "contexts": [ + { + "entity_id": "3fa3fa86-0907-4642-a57e-0fe30a19874d", + "entity_schema": "opportunity", + "is_primary": true + } + ], + "purposes": [ + "sales", + "onboarding" + ] + } + } } } } }, "responses": { "201": { - "description": "Success - when the flow execution is started successfully", + "description": "Flow execution started successfully. Returns the complete execution object including\nall phases, tasks, edges, and initial status.\n", "content": { "application/json": { "schema": { @@ -708,27 +846,53 @@ } }, "400": { - "description": "Validation Errors", + "description": "Validation error. Common causes include:\n- Missing required fields (flow_template_id, contexts)\n- Invalid flow_template_id (template not found)\n- Invalid entity context (entity not found)\n- Empty contexts array\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "flow_template_id is required" } } } }, "401": { - "description": "Unauthorized" + "description": "Authentication failed. The Bearer token is missing, expired, or invalid.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Unauthorized" + } + } + } }, "403": { - "description": "Forbidden" + "description": "Access forbidden. The authenticated user does not have permission to start\nexecutions for this flow template or organization.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Forbidden" + } + } + } }, "500": { - "description": "Other errors", + "description": "Internal server error. An unexpected error occurred while processing the request.\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Internal server error" } } } @@ -740,7 +904,7 @@ "get": { "operationId": "getFlowExecution", "summary": "getFlowExecution", - "description": "Get a full flow execution, included tasks, phases, edges & analytics.", + "description": "Retrieves a complete flow execution by ID, including all phases, tasks, edges, contexts, and analytics.\n\nThe response includes:\n- **Execution metadata**: ID, name, status, timestamps, assignees\n- **Phases**: Organizational groupings of tasks with progress tracking\n- **Tasks**: Individual work items with their status, assignees, and configurations\n- **Edges**: Connections between tasks defining the workflow graph\n- **Analytics**: Timing information (started, completed, closed timestamps)\n- **Contexts**: Linked entity references\n", "tags": [ "Flows V2" ], @@ -928,7 +1092,7 @@ "patch": { "operationId": "patchTask", "summary": "patchTask", - "description": "Changes various attributes of a flow task, like assignees, status, due date, etc.", + "description": "Updates attributes of a flow task including status, assignees, due date, and more.\n\n**Common use cases:**\n- Mark a task as completed or skipped\n- Assign or reassign users to a task\n- Update task due dates\n- Enable or disable a task\n- Revert execution to a previous task\n\n**Status transitions:**\n- `PENDING` -> `IN_PROGRESS`: User starts working on the task\n- `IN_PROGRESS` -> `COMPLETED`: User finishes the task\n- `PENDING` or `IN_PROGRESS` -> `SKIPPED`: Task is bypassed\n- `COMPLETED` or `SKIPPED` -> `PENDING`: Task is reopened (with revert_execution flag)\n\n**Reverting execution:**\nWhen updating a task that was already completed/skipped and comes before the current task,\nuse `revert_execution: true` to reset the flow back to that point. All subsequent tasks\nwill be reset to PENDING status.\n", "tags": [ "Flows V2" ], @@ -941,19 +1105,75 @@ } ], "requestBody": { - "description": "Patch Task Payload", + "description": "Task update payload with fields to modify", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PatchTaskReq" + }, + "examples": { + "markCompleted": { + "summary": "Mark task as completed", + "description": "Complete a task, advancing the workflow to the next task", + "value": { + "status": "COMPLETED" + } + }, + "markInProgress": { + "summary": "Mark task as in progress", + "description": "Indicate that work has started on this task", + "value": { + "status": "IN_PROGRESS" + } + }, + "skipTask": { + "summary": "Skip a task", + "description": "Bypass a task without completing it", + "value": { + "status": "SKIPPED" + } + }, + "assignUsers": { + "summary": "Assign users to task", + "description": "Assign one or more users to be responsible for this task", + "value": { + "assigned_to": [ + "10010730", + "10010731" + ] + } + }, + "updateDueDate": { + "summary": "Set task due date", + "description": "Set an explicit due date for the task", + "value": { + "due_date": "2024-06-15T17:00:00.000Z" + } + }, + "revertToPreviousTask": { + "summary": "Revert execution to previous task", + "description": "Reset workflow back to a previously completed task, clearing all subsequent progress", + "value": { + "status": "PENDING", + "revert_execution": true + } + }, + "decisionTaskBranch": { + "summary": "Select branch for decision task", + "description": "For manual decision tasks, specify which condition/branch to follow", + "value": { + "status": "COMPLETED", + "next_condition_id": "cond_branch_a" + } + } } } } }, "responses": { "200": { - "description": "Success - when task has been successfully updated", + "description": "Task updated successfully. Returns the updated task object with all current values.\nIf the status change triggers workflow advancement, the flow execution state is also updated.\n", "content": { "application/json": { "schema": { @@ -963,31 +1183,53 @@ } }, "400": { - "description": "Validation Errors", + "description": "Validation error. Common causes include:\n- Invalid status transition (e.g., COMPLETED to IN_PROGRESS without revert)\n- Invalid task_id\n- Invalid assigned_to user IDs\n- Invalid due_date format\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Invalid status transition from COMPLETED to IN_PROGRESS" } } } }, "401": { - "description": "Authentication Errors", + "description": "Authentication failed. The Bearer token is missing, expired, or invalid.\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Unauthorized" + } + } + } + }, + "404": { + "description": "Task or execution not found. The specified execution_id or task_id does not exist.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Task not found" } } } }, "500": { - "description": "Other errors", + "description": "Internal server error. An unexpected error occurred while processing the request.\n", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResp" + }, + "example": { + "message": "Internal server error" } } } @@ -1317,6 +1559,86 @@ } } }, + "/v2/flows/executions/{execution_id}/tasks/{task_id}/reconcile-automation": { + "post": { + "operationId": "reconcileAutomationTask", + "summary": "reconcileAutomationTask", + "description": "Reconciles an automation task's status against its linked automation execution.\n\nLooks the task up by id (whether or not it is currently a `crt_task`),\nfetches its automation execution, and — if the automation has reached a\nterminal state (success/failed/skipped) — corrects the task's `status`,\n`automation_config` and `analytics` to match and persists them. If the\ntask is the current task and the flow is still active, the flow is\nadvanced; otherwise only the task record is corrected.\n\nUsed by the \"Refresh status\" action to recover a task left `IN_PROGRESS`\nafter a missed or clobbered completion event. No-op (returns the\nexecution unchanged) when the task is already terminal or the automation\nis still running.\n", + "tags": [ + "Flows V2" + ], + "parameters": [ + { + "$ref": "#/components/parameters/ExecutionIdParam" + }, + { + "$ref": "#/components/parameters/TaskIdParam" + } + ], + "responses": { + "200": { + "description": "Success - returns the (possibly updated) flow execution", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FlowExecution" + } + } + } + }, + "400": { + "description": "Validation Errors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "401": { + "description": "Authentication Errors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "500": { + "description": "Other errors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + } + } + } + }, "/v2/flows/executions/{execution_id}/tasks/{task_id}/schedule/run-now": { "post": { "operationId": "runTaskScheduleNow", @@ -1468,8 +1790,8 @@ "type": "string" }, "required": true, - "description": "Id of the execution", - "example": "wd561" + "description": "Unique identifier for the flow execution. This ID is generated when the execution\nis created and remains constant throughout its lifecycle.\n", + "example": "exec_abc123def456" }, "TaskIdParam": { "in": "path", @@ -1478,8 +1800,8 @@ "type": "string" }, "required": true, - "description": "Id of the task", - "example": "7hj28a" + "description": "Unique identifier for a task within the execution. Each task has a unique ID\nthat distinguishes it from other tasks in the same execution.\n", + "example": "task_7hj28a" }, "PhaseIdParam": { "in": "path", @@ -1488,8 +1810,8 @@ "type": "string" }, "required": true, - "description": "Id of the phase", - "example": "9gjs2952j" + "description": "Unique identifier for a phase within the execution. Phases group related tasks\ntogether and track collective progress.\n", + "example": "phase_9gjs2952j" }, "ScheduleIdParam": { "in": "path", @@ -1498,7 +1820,7 @@ "type": "string" }, "required": true, - "description": "Id of the schedule", + "description": "Unique identifier for a scheduled task action. Schedules are created when tasks\nare configured with delayed or relative timing.\n", "example": "sch_8k2m9n4p" }, "SoftDeleteParam": { @@ -1509,7 +1831,7 @@ "default": false }, "required": false, - "description": "If true, the execution will NOT be deleted permanently, but rather kept for archive purpose.", + "description": "When true, the execution is marked as deleted but retained in storage for archival\nand audit purposes. When false (default), the execution is permanently removed.\nSoft-deleted executions do not appear in normal queries.\n", "example": true } }, @@ -2606,11 +2928,17 @@ }, "ErrorResp": { "type": "object", + "description": "Standard error response returned when an API request fails.\nContains a human-readable message describing the error.\n", "properties": { "message": { - "type": "string" + "type": "string", + "description": "Human-readable description of the error that occurred", + "example": "Validation failed: workflowId is required" } - } + }, + "required": [ + "message" + ] }, "ClosingReasonResp": { "type": "object", @@ -2944,19 +3272,23 @@ }, "StartFlowReq": { "type": "object", + "description": "Request payload for starting a new flow execution from a template.\n", "required": [ "flow_template_id", "contexts" ], "properties": { "flow_template_id": { - "type": "string" + "type": "string", + "description": "The unique identifier of the flow template (definition) to instantiate.\nThe template must exist and be accessible within the organization.\n", + "example": "tpl_abc123def456" }, "trigger": { "$ref": "#/components/schemas/FlowTrigger" }, "contexts": { "type": "array", + "description": "Entity references that this execution is linked to. At least one context\nis required. The primary context (is_primary: true) is used for condition\nevaluation and data mapping.\n", "minItems": 1, "items": { "$ref": "#/components/schemas/FlowContext" @@ -2967,7 +3299,7 @@ "items": { "type": "string" }, - "description": "An array of purposes to filter workflow phases." + "description": "Taxonomy purpose IDs to filter which phases and tasks are included in the execution.\nOnly phases/tasks tagged with matching purposes will be active. If empty or omitted,\nall phases and tasks from the template are included.\n" } } }, @@ -3101,6 +3433,11 @@ "properties": { "id": { "$ref": "#/components/schemas/TaskId" + }, + "crt_since": { + "type": "string", + "format": "date-time", + "description": "Timestamp when this task entered crt_tasks (i.e. became current). Used by the flow-healing-service as the authoritative gate for \"has this PENDING task been stuck long enough to heal?\". Using the task's own analytics.status_updated_at as the gate produced false positives because transitioning a task INTO crt_tasks does not change its status — so that timestamp can be hours old for a freshly-current task." } } } @@ -3170,6 +3507,10 @@ "singleClosingReasonSelection": { "type": "boolean", "description": "Indicates whether only a single closing reason can be selected when closing the flow execution" + }, + "linear": { + "type": "boolean", + "description": "Copied from the flow template at start. When true, task enablement is computed at runtime from the graph (a task is enabled only when all of its direct predecessor tasks in its branch are done) instead of from each task's explicit requirements." } } }, @@ -3410,6 +3751,14 @@ "schedule_id": { "type": "string", "description": "The id of the created schedule" + }, + "scheduled_at": { + "type": "string", + "description": "The resolved absolute timestamp (ISO 8601, UTC) at which the task is\narmed to run. Set by the backend when the schedule is armed and the\ntask transitions to SCHEDULED. Absent while the task is still\npending/unscheduled.\n\nNote: intentionally typed as plain `string` (not\n`format: date-time`). `schedule` is embedded in the AutomationTask /\nDecisionTask schemas; if a future request body ever accepts a task\n(or schedule) and `safeParse`s it, `format: date-time` would make\nopenapi-zod-client emit `z.string().datetime({ offset: true })` and\nreject any round-tripped value that is empty or tz-less — the exact\nmechanism behind the May 2026 due_date incident (513ed597 added the\nformat, ee574b43 activated it via an unrelated regen; see commit\n2c91ff35). This field is server-written via `toISOString()` so it is\nalways a valid UTC instant; the datetime validator adds no\nprotection, only latent risk.\n" + }, + "error_reason": { + "type": "string", + "description": "Set by the backend when scheduling this task FAILED (e.g. the\nreferenced date attribute is empty/unreadable, or the resolved fire\ntime is already in the past). A JSON string\n({ error_code, error_reason, error_info }) describing why, so the UI\ncan show a specific warning and ask the user to fix the date\nattribute and re-schedule. Cleared when the task is successfully\n(re-)armed. This is the decision-task counterpart of\nAutomationInfo.error_reason (decision tasks have no automation_config).\n" } } }, @@ -3467,6 +3816,14 @@ "schedule_id": { "type": "string", "description": "The id of the created schedule" + }, + "scheduled_at": { + "type": "string", + "description": "The resolved absolute timestamp (ISO 8601, UTC) at which the task is\narmed to run. Set by the backend when the schedule is armed and the\ntask transitions to SCHEDULED. Absent while the task is still\npending/unscheduled.\n\nNote: intentionally typed as plain `string` (not\n`format: date-time`). `schedule` is embedded in the AutomationTask /\nDecisionTask schemas; if a future request body ever accepts a task\n(or schedule) and `safeParse`s it, `format: date-time` would make\nopenapi-zod-client emit `z.string().datetime({ offset: true })` and\nreject any round-tripped value that is empty or tz-less — the exact\nmechanism behind the May 2026 due_date incident (513ed597 added the\nformat, ee574b43 activated it via an unrelated regen; see commit\n2c91ff35). This field is server-written via `toISOString()` so it is\nalways a valid UTC instant; the datetime validator adds no\nprotection, only latent risk.\n" + }, + "error_reason": { + "type": "string", + "description": "Set by the backend when scheduling this task FAILED (e.g. the\nreferenced date attribute is empty/unreadable, or the resolved fire\ntime is already in the past). A JSON string\n({ error_code, error_reason, error_info }) describing why, so the UI\ncan show a specific warning and ask the user to fix the date\nattribute and re-schedule. Cleared when the task is successfully\n(re-)armed. This is the decision-task counterpart of\nAutomationInfo.error_reason (decision tasks have no automation_config).\n" } }, "required": [ @@ -3576,6 +3933,10 @@ }, "error_reason": { "type": "string" + }, + "outcome": { + "type": "string", + "description": "Server-computed outcome read from the agent execution's structured_output (e.g. assigned, recommended, no_eligible_partner, missing_input). When the outcome means the work is not really done (no_eligible_partner, missing_input) the task is held as the current task instead of auto-completing, so the phase does not advance." } } }, @@ -3767,6 +4128,15 @@ }, "input_context": { "$ref": "#/components/schemas/AutomationInputContext" + }, + "heal_attempts": { + "type": "integer", + "description": "Internal — number of times flow-healing-service has attempted to re-trigger this task's lost automation dispatch. Used to cap retries and avoid an indefinite heal-on-every-read storm against a deterministically-failing automation flow." + }, + "last_heal_attempted_at": { + "type": "string", + "format": "date-time", + "description": "Internal — timestamp of the most recent heal attempt for this task. flow-healing-service uses this as a per-task debounce gate so the heal cannot fire more than once per HEAL_RETRY_COOLDOWN_MS regardless of how often the flow execution is read." } }, "required": [ @@ -3978,6 +4348,22 @@ "deleted" ] }, + "attributes": { + "type": "array", + "maxItems": 10, + "items": { + "type": "string" + }, + "description": "Multi-attribute mode. When present and length > 1, the statement is\nevaluated against every listed attribute and combined via\n`attributes_match`. All listed attributes must share the same\n`attribute_type`. Mutually exclusive with `attribute_sub_field`,\n`date_offset`, and `attribute_operation`. When absent or length === 1,\nthe legacy `attribute` field is used.\n" + }, + "attributes_match": { + "type": "string", + "enum": [ + "any", + "all" + ], + "description": "Inner connector across `attributes`. `any` (default) means at least\none attribute must satisfy the operator; `all` means every attribute\nmust satisfy it. Ignored when `attributes` is absent or has length < 2.\n" + }, "attribute_sub_field": { "type": "string", "description": "For complex attribute types, specifies which sub-field to extract (e.g., 'address', 'name', 'email_type')" @@ -4062,7 +4448,8 @@ "hours", "days", "weeks", - "months" + "months", + "years" ] }, "EnableRequirement": { @@ -4139,16 +4526,20 @@ }, "PatchTaskReq": { "type": "object", + "description": "Request payload for updating a task within a flow execution.\nAll fields are optional; only provided fields will be updated.\n", "properties": { "name": { - "type": "string" + "type": "string", + "description": "Display name of the task", + "example": "Review customer application" }, "status": { "$ref": "#/components/schemas/StepStatus" }, "due_date": { "type": "string", - "example": "2021-04-27T12:00:00.000Z" + "description": "Explicit due date for the task. Takes precedence over\ndue_date_config if both are provided.\n\nNote: intentionally typed as plain `string` (not\n`format: date-time`). For day/week/month-precision due\ndates the server stores a \"floating\" datetime without a\ntimezone designator (e.g. `2026-05-28T00:00:00.000`) so\nthat the UI can render it as a date in the user's local\ntimezone without shifting the displayed day. Tightening\nthis to `format: date-time` causes openapi-zod-client to\nemit `z.string().datetime({ offset: true })` in\n`validators-generated.ts`, which then trips\n`safeParse(body)` in `patch-task.ts` whenever the\nsidebar sends a stored task back with a tz-less\n`due_date`. See commit 4aca299c (Aug 2024) for the\noriginal date-only display rationale and the May 2026\nincident (513ed597 added the format, ee574b43\nunintentionally activated it via an unrelated regen) for\nthe history. Long-term, day-precision due dates should\nmigrate to a separate `format: date` field.\n", + "example": "2026-05-28T00:00:00.000" }, "due_date_config": { "$ref": "#/components/schemas/DueDateConfig" @@ -4158,7 +4549,7 @@ }, "enabled": { "type": "boolean", - "description": "flag for controlling enabled/disabled state of the task" + "description": "Controls whether the task is enabled (can be worked on) or disabled (grayed out).\nDisabled tasks cannot have their status changed until re-enabled.\n" }, "automation_config": { "$ref": "#/components/schemas/AutomationInfo" @@ -4178,11 +4569,17 @@ }, "next_condition_id": { "type": "string", - "description": "Condition to evaluate as true for a decision task with a manual trigger mode" + "description": "For decision tasks with manual trigger mode, specifies which condition/branch\nto follow when completing the task. The condition ID must match one of the\nconditions defined on the decision task.\n", + "example": "cond_branch_approved" }, "revert_execution": { "type": "boolean", - "description": "When patching an already completed/skipped task that comes before the current task, this flag controls whether to revert the execution:\n- `true`: The patched task becomes the current task AND all succeeding tasks are reset to PENDING (full revert)\n- `false` or omitted (undefined): The task is updated but the current task does not change and no downstream tasks are affected\nThis parameter is silently ignored when patching the current task or future tasks.\n", + "description": "Controls behavior when updating a task that was already completed/skipped and\ncomes before the current task in the workflow:\n- `true`: Reverts the execution - the patched task becomes the current task\n and all subsequent tasks are reset to PENDING status\n- `false` (default): Updates only this task without affecting workflow position\n or other tasks\n\n**Important:** This parameter is silently ignored when:\n- Patching the current task\n- Patching future tasks (tasks that haven't been reached yet)\n", + "default": false + }, + "completed_via_journey": { + "type": "boolean", + "description": "Request-only signal indicating the task is being completed as a\nresult of the user submitting the task's linked journey (journey\nauto-completion). When `true` and the task transitions to\n`COMPLETED`, the activity log records a journey-specific message\ninstead of the generic completion message. Not persisted on the task.\n", "default": false } } diff --git a/packages/cli/docs/access-token.md b/packages/cli/docs/access-token.md index e0da1807b..2eac8d84b 100644 --- a/packages/cli/docs/access-token.md +++ b/packages/cli/docs/access-token.md @@ -87,6 +87,7 @@ epilot access-token listAccessTokens --jsonata '$' "portal_user_id": "string", "assignments": ["123:owner"], "read_only": true, + "anonymize": true, "last_used": "2026-02-24" } ] @@ -106,9 +107,22 @@ epilot access-token listAccessTokens --jsonata '$' **Sample Call** +```bash +epilot access-token createAccessToken +``` + +With request body: + ```bash epilot access-token createAccessToken \ - -d '{"name":"Postman Access Token","token_type":"api","assignments":["123:owner"],"expires_in":3600,"read_only":true}' + -d '{ + "name": "Postman Access Token", + "token_type": "api", + "assignments": ["123:owner"], + "expires_in": 3600, + "read_only": true, + "anonymize": true +}' ``` Using stdin pipe: @@ -138,6 +152,7 @@ epilot access-token createAccessToken --jsonata '$' "portal_user_id": "string", "assignments": ["123:owner"], "read_only": true, + "anonymize": true, "last_used": "2026-02-24" } ``` @@ -191,6 +206,7 @@ epilot access-token revokeAccessToken -p id=api_5ZugdRXasLfWBypHi93Fk --jsonata "portal_user_id": "string", "assignments": ["123:owner"], "read_only": true, + "anonymize": true, "last_used": "2026-02-24" } ``` diff --git a/packages/cli/docs/ai-agents.md b/packages/cli/docs/ai-agents.md index 784262249..1ef5f1d3b 100644 --- a/packages/cli/docs/ai-agents.md +++ b/packages/cli/docs/ai-agents.md @@ -1,4 +1,4 @@ -# AI Agents API - OpenAPI 3.0 +# AI Agents API - **Base URL:** `https://ai-agents.sls.epilot.io` - **API Docs:** [https://docs.epilot.io/api/ai-agents](https://docs.epilot.io/api/ai-agents) @@ -44,12 +44,25 @@ epilot ai-agents listAgents **Agent Execution** - [`executeAgent`](#executeagent) — Executes an agent (system skill or custom agent). -- [`listExecutions`](#listexecutions) — GET /v1/executions -- [`getExecution`](#getexecution) — GET /v1/executions/{execution_id} -- [`cancelExecution`](#cancelexecution) — DELETE /v1/executions/{execution_id} +- [`executeAgentStream`](#executeagentstream) — Executes an agent with real-time streaming of tokens and tool events. +- [`listExecutions`](#listexecutions) — List executions +- [`getExecution`](#getexecution) — Get execution by ID +- [`cancelExecution`](#cancelexecution) — Cancel execution - [`getExecutionTrace`](#getexecutiontrace) — Returns the step-by-step reasoning and tool calls for ReAct mode executions. Returns empty iterations array for direct m +- [`getExecutionFeedback`](#getexecutionfeedback) — Returns the authenticated user's feedback for an execution. +- [`putExecutionFeedback`](#putexecutionfeedback) — Upserts thumbs up/down feedback and mirrors it to the execution's Langfuse trace. - [`approveExecution`](#approveexecution) — Approves a pending tool action when execution is in waiting_approval status - [`rejectExecution`](#rejectexecution) — Rejects a pending tool action when execution is in waiting_approval status +- [`streamExecution`](#streamexecution) — Reconnects to an execution's event stream after approval. Replays missed events from event log and continues streaming i + +**Chat** +- [`chat`](#chat) — Initiates a streaming chat session with an AI agent. Supports server-side conversation memory via conversationId or clie + +**Conversations** +- [`listConversations`](#listconversations) — Lists conversations for the authenticated user, sorted by most recent. +- [`getConversation`](#getconversation) — Retrieves a conversation and its message history. +- [`deleteConversation`](#deleteconversation) — Deletes a conversation and all its messages. +- [`submitConversationFeedback`](#submitconversationfeedback) — Records a thumbs up/down (with optional comment) for the assistant turn identified by its Langfuse trace id. The rating ### `listAgents` @@ -62,7 +75,7 @@ Lists agents from both system skills and custom agents. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `source` | query | "system" \| "custom" | No | Filter by agent source (system = pre-built skills, custom = user-created) | -| `availability` | query | "flows" \| "copilot" \| "all" | No | Filter by availability context (flows, copilot) | +| `availability` | query | "flows" \| "copilot" \| "portals" \| "all" | No | Filter by availability context (flows, copilot) | | `entity_schema` | query | string | No | Filter by allowed entity schema (e.g., "message" for email-related skills) | **Sample Call** @@ -98,7 +111,9 @@ epilot ai-agents listAgents --jsonata 'agents' "model_config": { "model_id": "anthropic.claude-3-5-sonnet-20241022-v2:0", "temperature": 0.7, - "max_tokens": 4096 + "max_tokens": 4096, + "thinking": false, + "thinking_budget": 10000 }, "max_iterations": 0, "execution_pattern": "direct", @@ -166,7 +181,9 @@ epilot ai-agents createAgent \ "model_config": { "model_id": "anthropic.claude-3-5-sonnet-20241022-v2:0", "temperature": 0.7, - "max_tokens": 4096 + "max_tokens": 4096, + "thinking": false, + "thinking_budget": 10000 }, "max_iterations": 10, "execution_pattern": "direct", @@ -226,7 +243,9 @@ epilot ai-agents createAgent --jsonata 'agent_id' "model_config": { "model_id": "anthropic.claude-3-5-sonnet-20241022-v2:0", "temperature": 0.7, - "max_tokens": 4096 + "max_tokens": 4096, + "thinking": false, + "thinking_budget": 10000 }, "max_iterations": 0, "execution_pattern": "direct", @@ -313,7 +332,9 @@ epilot ai-agents getAgentById -p agent_id=skill:email-categorizer --jsonata 'age "model_config": { "model_id": "anthropic.claude-3-5-sonnet-20241022-v2:0", "temperature": 0.7, - "max_tokens": 4096 + "max_tokens": 4096, + "thinking": false, + "thinking_budget": 10000 }, "max_iterations": 0, "execution_pattern": "direct", @@ -386,7 +407,9 @@ epilot ai-agents updateAgentById \ "model_config": { "model_id": "anthropic.claude-3-5-sonnet-20241022-v2:0", "temperature": 0.7, - "max_tokens": 4096 + "max_tokens": 4096, + "thinking": false, + "thinking_budget": 10000 }, "max_iterations": 1, "execution_pattern": "direct", @@ -452,7 +475,9 @@ epilot ai-agents updateAgentById -p agent_id=skill:email-categorizer --jsonata ' "model_config": { "model_id": "anthropic.claude-3-5-sonnet-20241022-v2:0", "temperature": 0.7, - "max_tokens": 4096 + "max_tokens": 4096, + "thinking": false, + "thinking_budget": 10000 }, "max_iterations": 0, "execution_pattern": "direct", @@ -634,7 +659,15 @@ epilot ai-agents executeAgent -p agent_id=skill:email-categorizer --jsonata 'exe "iteration_count": 0 }, "started_at": "1970-01-01T00:00:00.000Z", - "completed_at": "1970-01-01T00:00:00.000Z" + "completed_at": "1970-01-01T00:00:00.000Z", + "iterations": [ + { + "index": 0, + "tool": "string", + "status": "running", + "timestamp": "1970-01-01T00:00:00.000Z" + } + ] } ``` @@ -642,8 +675,79 @@ epilot ai-agents executeAgent -p agent_id=skill:email-categorizer --jsonata 'exe --- +### `executeAgentStream` + +Executes an agent with real-time streaming of tokens and tool events. + +`POST /v1/agents/{agent_id}/execute/stream` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `agent_id` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot ai-agents executeAgentStream \ + -p agent_id=skill:email-categorizer +``` + +With request body: + +```bash +epilot ai-agents executeAgentStream \ + -p agent_id=skill:email-categorizer \ + -d '{ + "input": { + "entity_id": "string", + "entity_schema": "string", + "workflow_id": "string", + "workflow_execution_id": "string", + "task_id": "string", + "custom_data": {}, + "flow_context": [ + { + "entity_id": "string", + "entity_schema": "string" + } + ] + }, + "parameters": {}, + "execution_mode_override": "automatic", + "execution_context": "flows", + "callback_url": "https://example.com/path", + "timeout_ms": 30000 +}' +``` + +Using positional args for path parameters: + +```bash +epilot ai-agents executeAgentStream skill:email-categorizer +``` + +Using stdin pipe: + +```bash +cat body.json | epilot ai-agents executeAgentStream -p agent_id=skill:email-categorizer +``` + +With JSONata filter: + +```bash +epilot ai-agents executeAgentStream -p agent_id=skill:email-categorizer --jsonata '$' +``` + +--- + ### `listExecutions` +List executions + `GET /v1/executions` **Parameters** @@ -687,7 +791,8 @@ epilot ai-agents listExecutions --jsonata 'executions' "pending_action": {}, "metrics": {}, "started_at": "1970-01-01T00:00:00.000Z", - "completed_at": "1970-01-01T00:00:00.000Z" + "completed_at": "1970-01-01T00:00:00.000Z", + "iterations": [] } ], "next_cursor": "string" @@ -700,6 +805,8 @@ epilot ai-agents listExecutions --jsonata 'executions' ### `getExecution` +Get execution by ID + `GET /v1/executions/{execution_id}` **Parameters** @@ -772,7 +879,15 @@ epilot ai-agents getExecution -p execution_id=123e4567-e89b-12d3-a456-4266141740 "iteration_count": 0 }, "started_at": "1970-01-01T00:00:00.000Z", - "completed_at": "1970-01-01T00:00:00.000Z" + "completed_at": "1970-01-01T00:00:00.000Z", + "iterations": [ + { + "index": 0, + "tool": "string", + "status": "running", + "timestamp": "1970-01-01T00:00:00.000Z" + } + ] } ``` @@ -782,6 +897,8 @@ epilot ai-agents getExecution -p execution_id=123e4567-e89b-12d3-a456-4266141740 ### `cancelExecution` +Cancel execution + `DELETE /v1/executions/{execution_id}` **Parameters** @@ -854,7 +971,15 @@ epilot ai-agents cancelExecution -p execution_id=123e4567-e89b-12d3-a456-4266141 "iteration_count": 0 }, "started_at": "1970-01-01T00:00:00.000Z", - "completed_at": "1970-01-01T00:00:00.000Z" + "completed_at": "1970-01-01T00:00:00.000Z", + "iterations": [ + { + "index": 0, + "tool": "string", + "status": "running", + "timestamp": "1970-01-01T00:00:00.000Z" + } + ] } ``` @@ -921,6 +1046,113 @@ epilot ai-agents getExecutionTrace -p execution_id=123e4567-e89b-12d3-a456-42661 --- +### `getExecutionFeedback` + +Returns the authenticated user's feedback for an execution. + +`GET /v1/executions/{execution_id}/feedback` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `execution_id` | path | string (uuid) | Yes | | + +**Sample Call** + +```bash +epilot ai-agents getExecutionFeedback \ + -p execution_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot ai-agents getExecutionFeedback 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot ai-agents getExecutionFeedback -p execution_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'feedback' +``` + +
+Sample Response + +```json +{ + "feedback": { + "rating": "up", + "comment": "string", + "user_id": "string", + "submitted_at": "1970-01-01T00:00:00.000Z" + } +} +``` + +
+ +--- + +### `putExecutionFeedback` + +Upserts thumbs up/down feedback and mirrors it to the execution's Langfuse trace. + +`PUT /v1/executions/{execution_id}/feedback` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `execution_id` | path | string (uuid) | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot ai-agents putExecutionFeedback \ + -p execution_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"rating":"up","comment":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot ai-agents putExecutionFeedback 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot ai-agents putExecutionFeedback -p execution_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot ai-agents putExecutionFeedback -p execution_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'feedback' +``` + +
+Sample Response + +```json +{ + "feedback": { + "rating": "up", + "comment": "string", + "user_id": "string", + "submitted_at": "1970-01-01T00:00:00.000Z" + } +} +``` + +
+ +--- + ### `approveExecution` Approves a pending tool action when execution is in waiting_approval status @@ -940,7 +1172,7 @@ Approves a pending tool action when execution is in waiting_approval status ```bash epilot ai-agents approveExecution \ -p execution_id=123e4567-e89b-12d3-a456-426614174000 \ - -d '{"reason":"string"}' + -d '{"reason":"string","approved_action_ids":["string"],"rejected_action_ids":["string"]}' ``` Using positional args for path parameters: @@ -1006,7 +1238,15 @@ epilot ai-agents approveExecution -p execution_id=123e4567-e89b-12d3-a456-426614 "iteration_count": 0 }, "started_at": "1970-01-01T00:00:00.000Z", - "completed_at": "1970-01-01T00:00:00.000Z" + "completed_at": "1970-01-01T00:00:00.000Z", + "iterations": [ + { + "index": 0, + "tool": "string", + "status": "running", + "timestamp": "1970-01-01T00:00:00.000Z" + } + ] } ``` @@ -1099,7 +1339,341 @@ epilot ai-agents rejectExecution -p execution_id=123e4567-e89b-12d3-a456-4266141 "iteration_count": 0 }, "started_at": "1970-01-01T00:00:00.000Z", - "completed_at": "1970-01-01T00:00:00.000Z" + "completed_at": "1970-01-01T00:00:00.000Z", + "iterations": [ + { + "index": 0, + "tool": "string", + "status": "running", + "timestamp": "1970-01-01T00:00:00.000Z" + } + ] +} +``` + + + +--- + +### `streamExecution` + +Reconnects to an execution's event stream after approval. Replays missed events from event log and continues streaming i + +`GET /v1/executions/{execution_id}/stream` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `execution_id` | path | string (uuid) | Yes | | +| `from_sequence` | query | number | No | Resume from this event sequence number (for reconnection) | + +**Sample Call** + +```bash +epilot ai-agents streamExecution \ + -p execution_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot ai-agents streamExecution 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot ai-agents streamExecution -p execution_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `chat` + +Initiates a streaming chat session with an AI agent. Supports server-side conversation memory via conversationId or clie + +`POST /v1/chat` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot ai-agents chat +``` + +With request body: + +```bash +epilot ai-agents chat \ + -d '{ + "agentId": "string", + "message": "string", + "conversationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "clientHistory": [ + { + "role": "user", + "content": "string", + "tool_calls": [ + { + "id": "string", + "name": "string", + "input": {}, + "output": "string" + } + ] + } + ], + "context": { + "entityId": "string", + "customData": {} + }, + "streaming": { + "mode": "updates", + "streamTokens": false, + "includeMetadata": false + } +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot ai-agents chat +``` + +With JSONata filter: + +```bash +epilot ai-agents chat --jsonata '$' +``` + +--- + +### `listConversations` + +Lists conversations for the authenticated user, sorted by most recent. + +`GET /v1/conversations` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `agent_id` | query | string | No | Filter by agent ID | +| `limit` | query | number | No | Maximum number of conversations to return | +| `cursor` | query | string | No | Pagination cursor | + +**Sample Call** + +```bash +epilot ai-agents listConversations +``` + +With JSONata filter: + +```bash +epilot ai-agents listConversations --jsonata 'conversations' +``` + +
+Sample Response + +```json +{ + "conversations": [ + { + "conversation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "user_id": "string", + "agent_id": "string", + "title": "string", + "message_count": 0, + "last_message": "string", + "last_message_at": "1970-01-01T00:00:00.000Z", + "context": { + "entityId": "string", + "customData": {} + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `getConversation` + +Retrieves a conversation and its message history. + +`GET /v1/conversations/{conversation_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `conversation_id` | path | string (uuid) | Yes | | +| `message_limit` | query | number | No | Maximum number of messages to return | + +**Sample Call** + +```bash +epilot ai-agents getConversation \ + -p conversation_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot ai-agents getConversation 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot ai-agents getConversation -p conversation_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'conversation' +``` + +
+Sample Response + +```json +{ + "conversation": { + "conversation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "user_id": "string", + "agent_id": "string", + "title": "string", + "message_count": 0, + "last_message": "string", + "last_message_at": "1970-01-01T00:00:00.000Z", + "context": { + "entityId": "string", + "customData": {} + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" + }, + "messages": [ + { + "conversation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "role": "user", + "content": "string", + "timestamp": "1970-01-01T00:00:00.000Z", + "tool_calls": [ + { + "id": "string", + "name": "string", + "input": {}, + "output": "string" + } + ], + "token_count": 0, + "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "feedback": { + "rating": "up", + "comment": "string", + "user_id": "string", + "submitted_at": "1970-01-01T00:00:00.000Z" + } + } + ] +} +``` + +
+ +--- + +### `deleteConversation` + +Deletes a conversation and all its messages. + +`DELETE /v1/conversations/{conversation_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `conversation_id` | path | string (uuid) | Yes | | + +**Sample Call** + +```bash +epilot ai-agents deleteConversation \ + -p conversation_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot ai-agents deleteConversation 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot ai-agents deleteConversation -p conversation_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `submitConversationFeedback` + +Records a thumbs up/down (with optional comment) for the assistant turn identified by its Langfuse trace id. The rating + +`POST /v1/conversations/{conversation_id}/feedback` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `conversation_id` | path | string (uuid) | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot ai-agents submitConversationFeedback \ + -p conversation_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"trace_id":"3fa85f64-5717-4562-b3fc-2c963f66afa6","rating":"up","comment":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot ai-agents submitConversationFeedback 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot ai-agents submitConversationFeedback -p conversation_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot ai-agents submitConversationFeedback -p conversation_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'feedback' +``` + +
+Sample Response + +```json +{ + "feedback": { + "rating": "up", + "comment": "string", + "user_id": "string", + "submitted_at": "1970-01-01T00:00:00.000Z" + } } ``` diff --git a/packages/cli/docs/app.md b/packages/cli/docs/app.md index 78be4bb6b..f5f5c8c05 100644 --- a/packages/cli/docs/app.md +++ b/packages/cli/docs/app.md @@ -43,6 +43,7 @@ epilot app getPublicFacingComponent -p appId=123e4567-e89b-12d3-a456-42661417400 - [`patchInstallation`](#patchinstallation) — Patch an installed app by its ID. - [`uninstall`](#uninstall) — Uninstall an app by its ID. - [`promoteVersion`](#promoteversion) — Update an installed app to a new version +- [`resolveOptions`](#resolveoptions) — Resolve the effective app-level options of an installation, including decrypted sensitive values (secrets). This endpoin **App Configuration** - [`listConfigurations`](#listconfigurations) — List all app configuration metadata owned by an organization. To get full app configuration details, use the /v1/app-con @@ -74,6 +75,9 @@ epilot app getPublicFacingComponent -p appId=123e4567-e89b-12d3-a456-42661417400 **App Proxy** - [`publicProxyGet`](#publicproxyget) — Forward a GET request to a registered proxy target from a public-facing component (e.g. journey blocks) - [`publicProxyPost`](#publicproxypost) — Forward a POST request to a registered proxy target from a public-facing component (e.g. journey blocks) +- [`publicProxyPut`](#publicproxyput) — Forward a PUT request to a registered proxy target from a public-facing component +- [`publicProxyPatch`](#publicproxypatch) — Forward a PATCH request to a registered proxy target from a public-facing component +- [`publicProxyDelete`](#publicproxydelete) — Forward a DELETE request to a registered proxy target from a public-facing component ### `getPublicFacingComponent` @@ -403,6 +407,35 @@ epilot app getPublicConfiguration -p appId=123e4567-e89b-12d3-a456-426614174000 "configuration": {} } ], + "functions": [ + { + "name": "string", + "type": "workflow", + "label": {}, + "description": {}, + "code": "string", + "schedule": "rate(30 minutes)", + "schedule_timezone": "Europe/Berlin", + "schedule_overlap": "skip", + "secrets": ["string"] + } + ], + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" + } + ], "is_beta": true, "deprecated_at": "string", "version": "string", @@ -424,6 +457,8 @@ epilot app getPublicConfiguration -p appId=123e4567-e89b-12d3-a456-426614174000 "app_id": "string", "owner_org_id": "string", "components": [], + "functions": [], + "options": [], "visibility": "private", "public": false, "pending": false, @@ -522,6 +557,35 @@ epilot app getConfiguration -p appId=123e4567-e89b-12d3-a456-426614174000 --json "configuration": {} } ], + "functions": [ + { + "name": "string", + "type": "workflow", + "label": {}, + "description": {}, + "code": "string", + "schedule": "rate(30 minutes)", + "schedule_timezone": "Europe/Berlin", + "schedule_overlap": "skip", + "secrets": ["string"] + } + ], + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" + } + ], "visibility": "private", "public": false, "pending": false, @@ -693,7 +757,8 @@ epilot app queryEvents \ "source": ["CUSTOM_JOURNEY_BLOCK"], "component_id": ["string"], "event_type": ["ERROR"], - "correlation_id": "string" + "correlation_id": "string", + "search": "string" }, "aggregation": { "group_by": ["source"], @@ -701,7 +766,8 @@ epilot app queryEvents \ }, "pagination": { "page": 1, - "page_size": 100 + "page_size": 100, + "before": "1970-01-01T00:00:00.000Z" }, "sort": { "field": "timestamp", @@ -743,7 +809,8 @@ epilot app queryEvents -p appId=123e4567-e89b-12d3-a456-426614174000 --jsonata ' "source": ["CUSTOM_JOURNEY_BLOCK"], "component_id": ["string"], "event_type": ["ERROR"], - "correlation_id": "string" + "correlation_id": "string", + "search": "string" }, "aggregation": { "group_by": ["source"], @@ -751,7 +818,8 @@ epilot app queryEvents -p appId=123e4567-e89b-12d3-a456-426614174000 --jsonata ' }, "pagination": { "page": 1, - "page_size": 100 + "page_size": 100, + "before": "1970-01-01T00:00:00.000Z" }, "sort": { "field": "timestamp", @@ -1045,25 +1113,9 @@ epilot app listVersions -p appId=123e4567-e89b-12d3-a456-426614174000 --jsonata { "app_id": "string", "owner_org_id": "string", - "components": [ - { - "component_type": "CUSTOM_JOURNEY_BLOCK", - "configuration": { - "override_dev_mode": { - "override_url": "http://localhost:3000" - }, - "component_url": "https://cdn.apps.com/123/v1.0.0/bundle.js", - "component_tag": "string", - "component_args": [ - { - "type": "text" - } - ], - "component_size": 0, - "component_mapping": {} - } - } - ], + "components": [], + "functions": [], + "options": [], "visibility": "private", "public": false, "pending": false, @@ -1072,28 +1124,9 @@ epilot app listVersions -p appId=123e4567-e89b-12d3-a456-426614174000 --jsonata "deprecated_at": "string", "changelog": "string", "review_status": "approved", - "role": { - "id": "string", - "grants": [ - { - "action": "string", - "resource": "string" - } - ] - }, - "blueprint_ref": { - "manifest_id": "string", - "job_id": "string", - "source_blueprint_file": "string" - }, - "version_audit": { - "created_at": "string", - "created_by": "string", - "updated_at": "string", - "updated_by": "string", - "versioned_at": "string", - "versioned_by": "string" - } + "role": {}, + "blueprint_ref": {}, + "version_audit": {} } ], "pagination": { @@ -1187,6 +1220,35 @@ epilot app getVersion -p appId=123e4567-e89b-12d3-a456-426614174000 -p version=e "configuration": {} } ], + "functions": [ + { + "name": "string", + "type": "workflow", + "label": {}, + "description": {}, + "code": "string", + "schedule": "rate(30 minutes)", + "schedule_timezone": "Europe/Berlin", + "schedule_overlap": "skip", + "secrets": ["string"] + } + ], + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" + } + ], "visibility": "private", "public": false, "pending": false, @@ -1238,11 +1300,72 @@ Patch an existing app version **Sample Call** +```bash +epilot app patchVersion \ + -p appId=123e4567-e89b-12d3-a456-426614174000 \ + -p version=example +``` + +With request body: + ```bash epilot app patchVersion \ -p appId=123e4567-e89b-12d3-a456-426614174000 \ -p version=example \ - -d '{"manifest_id":"string","role_id":"string","grants":[{"action":"string","resource":"string"}]}' + -d '{ + "manifest_id": "string", + "role_id": "string", + "grants": [ + { + "action": "string", + "resource": "string" + } + ], + "functions": [ + { + "name": "string", + "type": "workflow", + "label": { + "en": "string", + "de": "string" + }, + "description": { + "en": "string", + "de": "string" + }, + "code": "string", + "schedule": "rate(30 minutes)", + "schedule_timezone": "Europe/Berlin", + "schedule_overlap": "skip", + "secrets": ["string"] + } + ], + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [ + { + "key": "string", + "label": "string", + "description": "string", + "required": true, + "type": "text" + } + ], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" + } + ], + "changelog": "string" +}' ``` Using positional args for path parameters: @@ -1634,13 +1757,16 @@ Clone an existing app version to create a new version | `sourceVersion` | path | string | Yes | Source version to clone from | | `targetVersion` | path | string | Yes | Target version to create | +**Request Body** + **Sample Call** ```bash epilot app cloneVersion \ -p appId=123e4567-e89b-12d3-a456-426614174000 \ -p sourceVersion=example \ - -p targetVersion=example + -p targetVersion=example \ + -d '{"changelog":"string"}' ``` Using positional args for path parameters: @@ -1649,6 +1775,12 @@ Using positional args for path parameters: epilot app cloneVersion 123e4567-e89b-12d3-a456-426614174000 example example ``` +Using stdin pipe: + +```bash +cat body.json | epilot app cloneVersion -p appId=123e4567-e89b-12d3-a456-426614174000 -p sourceVersion=example -p targetVersion=example +``` + With JSONata filter: ```bash @@ -1680,7 +1812,7 @@ Retrieve a list of installed apps for the organization. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `componentType` | query | "CUSTOM_JOURNEY_BLOCK" \| "CUSTOM_PORTAL_BLOCK" \| "PORTAL_EXTENSION" \| "CUSTOM_FLOW_ACTION" \| "ERP_INFORM_TOOLKIT" \| "CUSTOM_CAPABILITY" \| "EXTERNAL_PRODUCT_CATALOG" \| "CUSTOM_PAGE" \| "API_PROXY" | No | Filter apps by specific component type | +| `componentType` | query | "CUSTOM_JOURNEY_BLOCK" \| "CUSTOM_PORTAL_BLOCK" \| "PORTAL_EXTENSION" \| "CUSTOM_FLOW_ACTION" \| "ERP_INFORM_TOOLKIT" \| "CUSTOM_CAPABILITY" \| "EXTERNAL_PRODUCT_CATALOG" \| "CUSTOM_PAGE" \| "API_PROXY" \| "EXTERNAL_VALUES" \| "APP_FUNCTION" | No | Filter apps by specific component type | | `enabled` | query | boolean | No | Filter apps by enabled status | | `page` | query | number | No | Page number for pagination | | `pageSize` | query | number | No | Number of items per page | @@ -1709,49 +1841,15 @@ epilot app listInstallations --jsonata 'apps' "owner_org_id": "string", "enabled": true, "name": "string", - "option_values": [ - { - "component_id": "string", - "options": [ - { - "key": "string", - "value": {} - } - ] - } - ], - "components": [ - { - "component_type": "CUSTOM_JOURNEY_BLOCK", - "configuration": { - "override_dev_mode": { - "override_url": "http://localhost:3000" - }, - "component_url": "https://cdn.apps.com/123/v1.0.0/bundle.js", - "component_tag": "string", - "component_args": [ - { - "type": "text" - } - ], - "component_size": 0, - "component_mapping": {} - } - } - ], + "icon_url": "string", + "option_values": [], + "components": [], + "functions": [], + "options": [], "installed_version": "string", "role": "string", - "blueprint_ref": { - "manifest_id": "string", - "job_id": "string", - "source_blueprint_file": "string" - }, - "installation_audit": { - "created_at": "string", - "created_by": "string", - "updated_at": "string", - "updated_by": "string" - }, + "blueprint_ref": {}, + "installation_audit": {}, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] } ], @@ -1808,34 +1906,46 @@ epilot app getInstallation -p appId=123e4567-e89b-12d3-a456-426614174000 --jsona "owner_org_id": "string", "enabled": true, "name": "string", + "icon_url": "string", "option_values": [ { "component_id": "string", - "options": [ - { - "key": "string", - "value": {} - } - ] + "options": [] } ], "components": [ { "component_type": "CUSTOM_JOURNEY_BLOCK", - "configuration": { - "override_dev_mode": { - "override_url": "http://localhost:3000" - }, - "component_url": "https://cdn.apps.com/123/v1.0.0/bundle.js", - "component_tag": "string", - "component_args": [ - { - "type": "text" - } - ], - "component_size": 0, - "component_mapping": {} - } + "configuration": {} + } + ], + "functions": [ + { + "name": "string", + "type": "workflow", + "label": {}, + "description": {}, + "code": "string", + "schedule": "rate(30 minutes)", + "schedule_timezone": "Europe/Berlin", + "schedule_overlap": "skip", + "secrets": ["string"] + } + ], + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" } ], "installed_version": "string", @@ -1930,34 +2040,46 @@ epilot app install -p appId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'app_ "owner_org_id": "string", "enabled": true, "name": "string", + "icon_url": "string", "option_values": [ { "component_id": "string", - "options": [ - { - "key": "string", - "value": {} - } - ] + "options": [] } ], "components": [ { "component_type": "CUSTOM_JOURNEY_BLOCK", - "configuration": { - "override_dev_mode": { - "override_url": "http://localhost:3000" - }, - "component_url": "https://cdn.apps.com/123/v1.0.0/bundle.js", - "component_tag": "string", - "component_args": [ - { - "type": "text" - } - ], - "component_size": 0, - "component_mapping": {} - } + "configuration": {} + } + ], + "functions": [ + { + "name": "string", + "type": "workflow", + "label": {}, + "description": {}, + "code": "string", + "schedule": "rate(30 minutes)", + "schedule_timezone": "Europe/Berlin", + "schedule_overlap": "skip", + "secrets": ["string"] + } + ], + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" } ], "installed_version": "string", @@ -2120,34 +2242,46 @@ epilot app promoteVersion -p appId=123e4567-e89b-12d3-a456-426614174000 -p versi "owner_org_id": "string", "enabled": true, "name": "string", + "icon_url": "string", "option_values": [ { "component_id": "string", - "options": [ - { - "key": "string", - "value": {} - } - ] + "options": [] } ], "components": [ { "component_type": "CUSTOM_JOURNEY_BLOCK", - "configuration": { - "override_dev_mode": { - "override_url": "http://localhost:3000" - }, - "component_url": "https://cdn.apps.com/123/v1.0.0/bundle.js", - "component_tag": "string", - "component_args": [ - { - "type": "text" - } - ], - "component_size": 0, - "component_mapping": {} - } + "configuration": {} + } + ], + "functions": [ + { + "name": "string", + "type": "workflow", + "label": {}, + "description": {}, + "code": "string", + "schedule": "rate(30 minutes)", + "schedule_timezone": "Europe/Berlin", + "schedule_overlap": "skip", + "secrets": ["string"] + } + ], + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" } ], "installed_version": "string", @@ -2171,6 +2305,82 @@ epilot app promoteVersion -p appId=123e4567-e89b-12d3-a456-426614174000 -p versi --- +### `resolveOptions` + +Resolve the effective app-level options of an installation, including decrypted sensitive values (secrets). This endpoin + +`POST /v1/app/{appId}/options/resolve` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `appId` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot app resolveOptions \ + -p appId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"keys":["string"]}' +``` + +Using positional args for path parameters: + +```bash +epilot app resolveOptions 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot app resolveOptions -p appId=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot app resolveOptions -p appId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'options' +``` + +
+Sample Response + +```json +{ + "options": [ + { + "key": "string", + "label": "string", + "required": true, + "description": "string", + "repeatable": true, + "fields": [ + { + "key": "string", + "label": "string", + "description": "string", + "required": true, + "type": "text" + } + ], + "value": {}, + "type": "text", + "sensitive": true, + "configured": true, + "lifted": true, + "value_updated_at": "string" + } + ] +} +``` + +
+ +--- + ### `ingestEvent` Internal endpoint for services to submit app events for analytic purposes @@ -2234,6 +2444,7 @@ Forward a GET request to a registered proxy target from a public-facing componen | `appId` | path | string | Yes | ID of the installed app | | `proxyName` | path | string | Yes | Name of the proxy target as defined in the app manifest | | `path` | path | string | Yes | Path to forward to the proxy target | +| `query` | query | object | No | Free-form query parameters, forwarded unchanged to the proxy target | **Sample Call** @@ -2271,6 +2482,7 @@ Forward a POST request to a registered proxy target from a public-facing compone | `appId` | path | string | Yes | ID of the installed app | | `proxyName` | path | string | Yes | Name of the proxy target as defined in the app manifest | | `path` | path | string | Yes | Path to forward to the proxy target | +| `query` | query | object | No | Free-form query parameters, forwarded unchanged to the proxy target | **Request Body** @@ -2303,3 +2515,135 @@ epilot app publicProxyPost -p appId=123e4567-e89b-12d3-a456-426614174000 -p prox ``` --- + +### `publicProxyPut` + +Forward a PUT request to a registered proxy target from a public-facing component + +`PUT /v1/public/app/{appId}/proxy/{proxyName}/{path}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `appId` | path | string | Yes | ID of the installed app | +| `proxyName` | path | string | Yes | Name of the proxy target as defined in the app manifest | +| `path` | path | string | Yes | Path to forward to the proxy target | +| `query` | query | object | No | Free-form query parameters, forwarded unchanged to the proxy target | + +**Request Body** + +**Sample Call** + +```bash +epilot app publicProxyPut \ + -p appId=123e4567-e89b-12d3-a456-426614174000 \ + -p proxyName=example \ + -p path=example \ + -d '{}' +``` + +Using positional args for path parameters: + +```bash +epilot app publicProxyPut 123e4567-e89b-12d3-a456-426614174000 example example +``` + +Using stdin pipe: + +```bash +cat body.json | epilot app publicProxyPut -p appId=123e4567-e89b-12d3-a456-426614174000 -p proxyName=example -p path=example +``` + +With JSONata filter: + +```bash +epilot app publicProxyPut -p appId=123e4567-e89b-12d3-a456-426614174000 -p proxyName=example -p path=example --jsonata '$' +``` + +--- + +### `publicProxyPatch` + +Forward a PATCH request to a registered proxy target from a public-facing component + +`PATCH /v1/public/app/{appId}/proxy/{proxyName}/{path}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `appId` | path | string | Yes | ID of the installed app | +| `proxyName` | path | string | Yes | Name of the proxy target as defined in the app manifest | +| `path` | path | string | Yes | Path to forward to the proxy target | +| `query` | query | object | No | Free-form query parameters, forwarded unchanged to the proxy target | + +**Request Body** + +**Sample Call** + +```bash +epilot app publicProxyPatch \ + -p appId=123e4567-e89b-12d3-a456-426614174000 \ + -p proxyName=example \ + -p path=example \ + -d '{}' +``` + +Using positional args for path parameters: + +```bash +epilot app publicProxyPatch 123e4567-e89b-12d3-a456-426614174000 example example +``` + +Using stdin pipe: + +```bash +cat body.json | epilot app publicProxyPatch -p appId=123e4567-e89b-12d3-a456-426614174000 -p proxyName=example -p path=example +``` + +With JSONata filter: + +```bash +epilot app publicProxyPatch -p appId=123e4567-e89b-12d3-a456-426614174000 -p proxyName=example -p path=example --jsonata '$' +``` + +--- + +### `publicProxyDelete` + +Forward a DELETE request to a registered proxy target from a public-facing component + +`DELETE /v1/public/app/{appId}/proxy/{proxyName}/{path}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `appId` | path | string | Yes | ID of the installed app | +| `proxyName` | path | string | Yes | Name of the proxy target as defined in the app manifest | +| `path` | path | string | Yes | Path to forward to the proxy target | +| `query` | query | object | No | Free-form query parameters, forwarded unchanged to the proxy target | + +**Sample Call** + +```bash +epilot app publicProxyDelete \ + -p appId=123e4567-e89b-12d3-a456-426614174000 \ + -p proxyName=example \ + -p path=example +``` + +Using positional args for path parameters: + +```bash +epilot app publicProxyDelete 123e4567-e89b-12d3-a456-426614174000 example example +``` + +With JSONata filter: + +```bash +epilot app publicProxyDelete -p appId=123e4567-e89b-12d3-a456-426614174000 -p proxyName=example -p path=example --jsonata '$' +``` + +--- diff --git a/packages/cli/docs/automation.md b/packages/cli/docs/automation.md index 361f96d5b..2c3d84528 100644 --- a/packages/cli/docs/automation.md +++ b/packages/cli/docs/automation.md @@ -46,6 +46,7 @@ epilot automation searchFlows **executions** - [`getExecutions`](#getexecutions) — List automation executions - [`startExecution`](#startexecution) — Start new automation execution +- [`searchExecutions`](#searchexecutions) — Search automation executions of an entity with cursor-based pagination. - [`getExecution`](#getexecution) — Get automation execution - [`cancelExecution`](#cancelexecution) — Cancel automation execution - [`retriggerAction`](#retriggeraction) — Retry a specific automation execution action which failed / is stuck. @@ -72,6 +73,7 @@ Search available automation flows | `from` | query | number | No | Pagination: starting for results | | `trigger_source_id` | query | string | No | Trigger source identifier | | `target_workflow` | query | string | No | Filter by target workflow ID. Returns only automations that have a trigger-workflow action targeting this workflow. | +| `trigger_event_name` | query | string | No | Filter by Event Catalog event name. Returns only automations that have an event_catalog trigger for this event. | | `include_flows` | query | boolean | No | Include flow automations in the response | **Sample Call** @@ -306,8 +308,10 @@ epilot automation getExecutions --jsonata 'results[0]' "version": 2, "trigger_event": {}, "workflow_context": {}, + "workflow_wait_context": {}, "loops": [], - "loop_state": {} + "loop_state": {}, + "chain": ["string"] } ] } @@ -347,13 +351,15 @@ epilot automation startExecution \ "parent_task_id": "string", "depth": 0 }, + "_automation_chain": ["string"], "entity_contexts": [ { "entity_id": "string", "entity_schema": "string", "is_primary": true } - ] + ], + "trigger_user_id": "10006129" }, "flow_execution_id": "string", "flow_automation_task_id": "string" @@ -400,8 +406,10 @@ epilot automation startExecution --jsonata 'id' { "id": "string", "schedule_id": "string", + "evaluation_order": "AFTER_SCHEDULE", "evaluationResult": true, "error_output": {}, + "allow_failure": true, "statements": [] } ], @@ -481,9 +489,16 @@ epilot automation startExecution --jsonata 'id' "parent_task_id": "string", "depth": 0 }, + "_automation_chain": ["string"], "entity_contexts": [ {} - ] + ], + "trigger_user_id": "10006129" + }, + "workflow_wait_context": { + "workflow_execution_id": "string", + "workflow_task_id": "string", + "source": "journey_submission" }, "loops": [ { @@ -493,7 +508,76 @@ epilot automation startExecution --jsonata 'id' "length": 0 } ], - "loop_state": {} + "loop_state": {}, + "chain": ["string"] +} +``` + +
+ +--- + +### `searchExecutions` + +Search automation executions of an entity with cursor-based pagination. + +`POST /v1/automation/executions:search` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot automation searchExecutions \ + -d '{"entity_id":"e3d3ebac-baab-4395-abf4-50b5bf1f8b74","include_flows":false,"size":25,"cursor":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot automation searchExecutions +``` + +With JSONata filter: + +```bash +epilot automation searchExecutions --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "total": 0, + "results": [ + { + "id": "9baf184f-bc81-4128-bca3-d974c90a12c4", + "execution_status": "pending", + "entity_id": "e3d3ebac-baab-4395-abf4-50b5bf1f8b74", + "activity_id": "e3d3ebac-baab-4395-abf4-50b5bf1f8b74", + "entity_snapshot": {}, + "org_id": "e3d3ebac-baab-4395-abf4-50b5bf1f8b74", + "flow_id": "7791b04a-16d2-44a2-9af9-2d59c25c512f", + "flow_name": "Handle contact form", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "current_action_id": "9ec3711b-db63-449c-b894-54d5bb622a8f", + "conditions": [], + "schedules": [], + "actions": [], + "resume_token": "eyJraWQiOiJrZXkifQ==", + "trigger_context": {}, + "version": 2, + "trigger_event": {}, + "workflow_context": {}, + "workflow_wait_context": {}, + "loops": [], + "loop_state": {}, + "chain": ["string"] + } + ], + "next_cursor": "string" } ``` @@ -781,8 +865,10 @@ epilot automation getExecution -p execution_id=9baf184f-bc81-4128-bca3-d974c90a1 { "id": "string", "schedule_id": "string", + "evaluation_order": "AFTER_SCHEDULE", "evaluationResult": true, "error_output": {}, + "allow_failure": true, "statements": [] } ], @@ -862,9 +948,16 @@ epilot automation getExecution -p execution_id=9baf184f-bc81-4128-bca3-d974c90a1 "parent_task_id": "string", "depth": 0 }, + "_automation_chain": ["string"], "entity_contexts": [ {} - ] + ], + "trigger_user_id": "10006129" + }, + "workflow_wait_context": { + "workflow_execution_id": "string", + "workflow_task_id": "string", + "source": "journey_submission" }, "loops": [ { @@ -874,7 +967,8 @@ epilot automation getExecution -p execution_id=9baf184f-bc81-4128-bca3-d974c90a1 "length": 0 } ], - "loop_state": {} + "loop_state": {}, + "chain": ["string"] } ``` @@ -941,8 +1035,10 @@ epilot automation cancelExecution -p execution_id=9baf184f-bc81-4128-bca3-d974c9 { "id": "string", "schedule_id": "string", + "evaluation_order": "AFTER_SCHEDULE", "evaluationResult": true, "error_output": {}, + "allow_failure": true, "statements": [] } ], @@ -1022,9 +1118,16 @@ epilot automation cancelExecution -p execution_id=9baf184f-bc81-4128-bca3-d974c9 "parent_task_id": "string", "depth": 0 }, + "_automation_chain": ["string"], "entity_contexts": [ {} - ] + ], + "trigger_user_id": "10006129" + }, + "workflow_wait_context": { + "workflow_execution_id": "string", + "workflow_task_id": "string", + "source": "journey_submission" }, "loops": [ { @@ -1034,7 +1137,8 @@ epilot automation cancelExecution -p execution_id=9baf184f-bc81-4128-bca3-d974c9 "length": 0 } ], - "loop_state": {} + "loop_state": {}, + "chain": ["string"] } ``` @@ -1164,12 +1268,20 @@ epilot automation resumeExecutionWithToken --jsonata 'execution' "workflow_exec_task_id": "string", "workflow_role": "trigger_workflow", "_execution_chain": {}, - "entity_contexts": [] + "_automation_chain": ["string"], + "entity_contexts": [], + "trigger_user_id": "10006129" + }, + "workflow_wait_context": { + "workflow_execution_id": "string", + "workflow_task_id": "string", + "source": "journey_submission" }, "loops": [ {} ], - "loop_state": {} + "loop_state": {}, + "chain": ["string"] }, "resumedAction": { "id": "9ec3711b-db63-449c-b894-54d5bb622a8f", @@ -1179,11 +1291,13 @@ epilot automation resumeExecutionWithToken --jsonata 'execution' "config": { "mapping_config": {}, "target_schema": "string", + "use_uniqueness_criteria": true, "target_unique": ["string"], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] }, "allow_failure": true, "created_automatically": true, diff --git a/packages/cli/docs/billing.md b/packages/cli/docs/billing.md index 0e1b8edef..881254fc1 100644 --- a/packages/cli/docs/billing.md +++ b/packages/cli/docs/billing.md @@ -3,7 +3,7 @@ - **Base URL:** `https://billing.sls.epilot.io` - **API Docs:** [https://docs.epilot.io/api/billing](https://docs.epilot.io/api/billing) -API to manage billing data for epilot contracts and orders +API to manage billing data for epilot contracts and orders. ## Quick Start @@ -36,24 +36,32 @@ epilot billing getBillingEvents ## Operations **Billing Events** -- [`getBillingEvents`](#getbillingevents) — Get and filter billing events such as installments and reimbursements. -- [`createBillingEvent`](#createbillingevent) — Create a new billing event. -- [`getBillingEvent`](#getbillingevent) — Get a single billing event by ID. -- [`updateBillingEvent`](#updatebillingevent) — Update an existing billing event. -- [`deleteBillingEvent`](#deletebillingevent) — Delete an existing billing event. -- [`getBillingEventByExternalId`](#getbillingeventbyexternalid) — Get a single billing event by External ID. +- [`getBillingEvents`](#getbillingevents) — Retrieve and filter billing events (Buchungssätze) such as installments (Abschlagszahlungen), +- [`createBillingEvent`](#createbillingevent) — Create a new billing event (Buchungssatz) such as an installment (Abschlagszahlung), +- [`getBillingEvent`](#getbillingevent) — Retrieve a single billing event (Buchungssatz) by its unique ID. +- [`updateBillingEvent`](#updatebillingevent) — Update an existing billing event (Buchungssatz). +- [`deleteBillingEvent`](#deletebillingevent) — Delete an existing billing event (Buchungssatz). +- [`getBillingEventByExternalId`](#getbillingeventbyexternalid) — Retrieve a billing event (Buchungssatz) by its external system identifier. **Contracts** -- [`createContractEntity`](#createcontractentity) — Create a new contract entity. -- [`updateContractEntity`](#updatecontractentity) — Update an existing contract entity. -- [`deleteContractEntity`](#deletecontractentity) — Delete an existing contract entity. +- [`createContractEntity`](#createcontractentity) — Create a new contract entity (Vertrag) for billing purposes. +- [`updateContractEntity`](#updatecontractentity) — Update an existing contract entity (Vertrag). +- [`deleteContractEntity`](#deletecontractentity) — Delete an existing contract entity (Vertrag). + +**Pricing Information** +- [`getContractPricingInformation`](#getcontractpricinginformation) — Get current pricing information and recent configuration history for a Contract. +- [`getBillingAccountPricingInformation`](#getbillingaccountpricinginformation) — Get current pricing information for the active Contracts linked to a Billing Account. + +**Configuration History** +- [`getContractConfigurationHistory`](#getcontractconfigurationhistory) — Get billing configuration history for a Contract. +- [`getBillingAccountConfigurationHistory`](#getbillingaccountconfigurationhistory) — Get merged billing configuration history for active Contracts linked to a Billing Account. **Balance** -- [`getCustomerBalance`](#getcustomerbalance) — Get total balance across all contracts and orders of a customer entity. +- [`getCustomerBalance`](#getcustomerbalance) — Retrieve the total balance (Kontostand) across all contracts and orders for a customer. ### `getBillingEvents` -Get and filter billing events such as installments and reimbursements. +Retrieve and filter billing events (Buchungssätze) such as installments (Abschlagszahlungen), `GET /v1/billing/events` @@ -61,12 +69,16 @@ Get and filter billing events such as installments and reimbursements. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `from` | query | number | No | | -| `size` | query | number | No | | -| `entity_id` | query | string[] | No | Entity ID to filter billing events by | -| `event_type` | query | "installment" \| "reimbursement" | No | | -| `date_after` | query | string (date-time) | No | | -| `date_before` | query | string (date-time) | No | | +| `from` | query | number | No | Pagination offset - number of results to skip | +| `size` | query | number | No | Maximum number of results to return per page | +| `entity_id` | query | string[] | No | Filter billing events by one or more entity IDs (e.g., contract or order IDs) | +| `contact_id` | query | string | No | Filter billing events by customer contact ID (Kundennummer) | +| `event_type` | query | "installment" \| "reimbursement" | No | Filter by billing event type (Buchungsart): +- `installment`: Abschlagszahlung (scheduled payment due) +- `reimbursement`: Rückerstattung (refund to customer) + | +| `date_after` | query | string (date-time) | No | Filter billing events with booking date (Buchungsdatum) after this timestamp | +| `date_before` | query | string (date-time) | No | Filter billing events with booking date (Buchungsdatum) before this timestamp | **Sample Call** @@ -85,30 +97,18 @@ epilot billing getBillingEvents --jsonata 'results[0]' ```json { - "hits": 0, + "hits": 42, "results": [ { - "billing_amount": 10050, - "billing_amount_decimal": "100.50", - "billing_currency": "EUR", - "external_id": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", - "contract": { - "$relation": [ - { - "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" - } - ] - }, - "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", "type": "installment", - "due_date": "1970-01-01T00:00:00.000Z", - "paid_date": "1970-01-01T00:00:00.000Z" + "direction": "debit", + "note": "July power & gas installment payment", + "status": "open", + "booking_date": "2025-07-10", + "due_date": "2025-07-10", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" } ] } @@ -120,7 +120,7 @@ epilot billing getBillingEvents --jsonata 'results[0]' ### `createBillingEvent` -Create a new billing event. +Create a new billing event (Buchungssatz) such as an installment (Abschlagszahlung), `POST /v1/billing/events` @@ -137,27 +137,15 @@ With request body: ```bash epilot billing createBillingEvent \ -d '{ - "billing_amount": 10050, - "billing_amount_decimal": "100.50", - "billing_currency": "EUR", - "external_id": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", - "contract": { - "$relation": [ - { - "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" - } - ] - }, - "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", "type": "installment", - "due_date": "1970-01-01T00:00:00.000Z", - "paid_date": "1970-01-01T00:00:00.000Z" + "direction": "debit", + "note": "July power & gas installment payment", + "status": "open", + "booking_date": "2025-07-10", + "due_date": "2025-07-10", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" }' ``` @@ -178,27 +166,15 @@ epilot billing createBillingEvent --jsonata '$' ```json { - "billing_amount": 10050, - "billing_amount_decimal": "100.50", - "billing_currency": "EUR", - "external_id": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", - "contract": { - "$relation": [ - { - "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" - } - ] - }, - "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", "type": "installment", - "due_date": "1970-01-01T00:00:00.000Z", - "paid_date": "1970-01-01T00:00:00.000Z" + "direction": "debit", + "note": "July power & gas installment payment", + "status": "open", + "booking_date": "2025-07-10", + "due_date": "2025-07-10", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" } ``` @@ -208,7 +184,7 @@ epilot billing createBillingEvent --jsonata '$' ### `getBillingEvent` -Get a single billing event by ID. +Retrieve a single billing event (Buchungssatz) by its unique ID. `GET /v1/billing/events/{id}` @@ -216,25 +192,25 @@ Get a single billing event by ID. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `id` | path | string | Yes | ID of the billing event to get | +| `id` | path | string | Yes | Unique identifier of the billing event (Buchungssatz-ID) | **Sample Call** ```bash epilot billing getBillingEvent \ - -p id=123e4567-e89b-12d3-a456-426614174000 + -p id=5da0a718-c822-403d-9f5d-20d4584e0528 ``` Using positional args for path parameters: ```bash -epilot billing getBillingEvent 123e4567-e89b-12d3-a456-426614174000 +epilot billing getBillingEvent 5da0a718-c822-403d-9f5d-20d4584e0528 ``` With JSONata filter: ```bash -epilot billing getBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot billing getBillingEvent -p id=5da0a718-c822-403d-9f5d-20d4584e0528 --jsonata '$' ```
@@ -242,27 +218,15 @@ epilot billing getBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 --json ```json { - "billing_amount": 10050, - "billing_amount_decimal": "100.50", - "billing_currency": "EUR", - "external_id": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", - "contract": { - "$relation": [ - { - "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" - } - ] - }, - "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", "type": "installment", - "due_date": "1970-01-01T00:00:00.000Z", - "paid_date": "1970-01-01T00:00:00.000Z" + "direction": "debit", + "note": "July power & gas installment payment", + "status": "open", + "booking_date": "2025-07-10", + "due_date": "2025-07-10", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" } ``` @@ -272,7 +236,7 @@ epilot billing getBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 --json ### `updateBillingEvent` -Update an existing billing event. +Update an existing billing event (Buchungssatz). `PATCH /v1/billing/events/{id}` @@ -280,7 +244,7 @@ Update an existing billing event. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `id` | path | string | Yes | ID of the billing event to update | +| `id` | path | string | Yes | Unique identifier of the billing event to update | **Request Body** (required) @@ -288,19 +252,21 @@ Update an existing billing event. ```bash epilot billing updateBillingEvent \ - -p id=123e4567-e89b-12d3-a456-426614174000 + -p id=5da0a718-c822-403d-9f5d-20d4584e0528 ``` With request body: ```bash epilot billing updateBillingEvent \ - -p id=123e4567-e89b-12d3-a456-426614174000 \ + -p id=5da0a718-c822-403d-9f5d-20d4584e0528 \ -d '{ - "billing_amount": 10050, - "billing_amount_decimal": "100.50", + "type": "installment", + "direction": "debit", + "billing_amount": 10000, + "billing_amount_decimal": "100.00", "billing_currency": "EUR", - "external_id": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", + "external_id": "SAP-54321", "contract": { "$relation": [ { @@ -308,35 +274,43 @@ epilot billing updateBillingEvent \ } ] }, - "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", - "type": "installment", - "due_date": "1970-01-01T00:00:00.000Z", - "paid_date": "1970-01-01T00:00:00.000Z" + "booking_date": "2025-06-15", + "due_date": "2025-06-30", + "paid_date": "2025-06-15T10:00:00Z", + "status": "closed", + "related_event": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", + "external_link": { + "href": "https://billing.example.com/invoices/12345", + "title": "Invoice 12345" + }, + "attachments": { + "$relation": [ + { + "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" + } + ] + }, + "note": "Teilzahlung für Abschlag Juni", + "internal_note": "Rückmeldung von SAP: Betrag aus Zahlungsavis 2025-06-14 übernommen" }' ``` Using positional args for path parameters: ```bash -epilot billing updateBillingEvent 123e4567-e89b-12d3-a456-426614174000 +epilot billing updateBillingEvent 5da0a718-c822-403d-9f5d-20d4584e0528 ``` Using stdin pipe: ```bash -cat body.json | epilot billing updateBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 +cat body.json | epilot billing updateBillingEvent -p id=5da0a718-c822-403d-9f5d-20d4584e0528 ``` With JSONata filter: ```bash -epilot billing updateBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot billing updateBillingEvent -p id=5da0a718-c822-403d-9f5d-20d4584e0528 --jsonata '$' ```
@@ -344,27 +318,15 @@ epilot billing updateBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 --j ```json { - "billing_amount": 10050, - "billing_amount_decimal": "100.50", - "billing_currency": "EUR", - "external_id": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", - "contract": { - "$relation": [ - { - "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" - } - ] - }, - "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", "type": "installment", - "due_date": "1970-01-01T00:00:00.000Z", - "paid_date": "1970-01-01T00:00:00.000Z" + "direction": "debit", + "note": "July power & gas installment payment", + "status": "open", + "booking_date": "2025-07-10", + "due_date": "2025-07-10", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" } ``` @@ -374,7 +336,7 @@ epilot billing updateBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 --j ### `deleteBillingEvent` -Delete an existing billing event. +Delete an existing billing event (Buchungssatz). `DELETE /v1/billing/events/{id}` @@ -382,32 +344,32 @@ Delete an existing billing event. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `id` | path | string | Yes | ID of the billing event to delete | +| `id` | path | string | Yes | Unique identifier of the billing event to delete | **Sample Call** ```bash epilot billing deleteBillingEvent \ - -p id=123e4567-e89b-12d3-a456-426614174000 + -p id=5da0a718-c822-403d-9f5d-20d4584e0528 ``` Using positional args for path parameters: ```bash -epilot billing deleteBillingEvent 123e4567-e89b-12d3-a456-426614174000 +epilot billing deleteBillingEvent 5da0a718-c822-403d-9f5d-20d4584e0528 ``` With JSONata filter: ```bash -epilot billing deleteBillingEvent -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot billing deleteBillingEvent -p id=5da0a718-c822-403d-9f5d-20d4584e0528 --jsonata '$' ``` --- ### `getBillingEventByExternalId` -Get a single billing event by External ID. +Retrieve a billing event (Buchungssatz) by its external system identifier. `GET /v1/billing/external/{external_id}` @@ -415,25 +377,27 @@ Get a single billing event by External ID. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `external_id` | path | string | Yes | ID of the billing event to get | +| `external_id` | path | string | Yes | External system identifier for the billing event. +For example, a SAP document number or payment processor reference ID. + | **Sample Call** ```bash epilot billing getBillingEventByExternalId \ - -p external_id=123e4567-e89b-12d3-a456-426614174000 + -p external_id=SAP-54321 ``` Using positional args for path parameters: ```bash -epilot billing getBillingEventByExternalId 123e4567-e89b-12d3-a456-426614174000 +epilot billing getBillingEventByExternalId SAP-54321 ``` With JSONata filter: ```bash -epilot billing getBillingEventByExternalId -p external_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot billing getBillingEventByExternalId -p external_id=SAP-54321 --jsonata '$' ```
@@ -441,27 +405,15 @@ epilot billing getBillingEventByExternalId -p external_id=123e4567-e89b-12d3-a45 ```json { - "billing_amount": 10050, - "billing_amount_decimal": "100.50", - "billing_currency": "EUR", - "external_id": "d4fb2a4e-3f74-4fc4-8fba-6fdaaaa3b08e", - "contract": { - "$relation": [ - { - "entity_id": "f589786b-3024-43cd-9cb3-5a3c953f2896" - } - ] - }, - "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", "type": "installment", - "due_date": "1970-01-01T00:00:00.000Z", - "paid_date": "1970-01-01T00:00:00.000Z" + "direction": "debit", + "note": "July power & gas installment payment", + "status": "open", + "booking_date": "2025-07-10", + "due_date": "2025-07-10", + "billing_amount": 5000, + "billing_amount_decimal": "50.00", + "billing_currency": "EUR" } ``` @@ -471,7 +423,7 @@ epilot billing getBillingEventByExternalId -p external_id=123e4567-e89b-12d3-a45 ### `createContractEntity` -Create a new contract entity. +Create a new contract entity (Vertrag) for billing purposes. `POST /v1/billing/contracts` @@ -489,32 +441,32 @@ With request body: epilot billing createContractEntity \ -d '{ "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", - "contract_name": "Grid Contract", - "contract_number": "12345", - "status": "approved", - "description": "This contract is for the supply of widgets.", - "account_number": "67890", + "_title": "Abschlagszahlung Juli 2025", + "_org": "123456", + "_schema": "billing_event", + "_tags": ["billing", "energy"], + "_created_at": "2025-06-15T10:30:00Z", + "_updated_at": "2025-06-15T14:45:00Z", + "contract_name": "Stromvertrag Haushalt", + "contract_number": "STR-2025-001234", + "status": "active", + "description": "Haushaltsstrom-Tarif mit 24 Monaten Preisgarantie", + "account_number": "KD-67890", "branch": "power", - "billing_address": "123 Main St, Anytown", - "delivery_address": "456 Elm St, Anytown", - "additional_addresses": "789 Oak St, Anytown", - "termination_date": "2022-01-01", - "termination_reason": "Non-payment", + "billing_address": "Musterstraße 123, 50667 Köln", + "delivery_address": "Musterstraße 123, 50667 Köln", + "additional_addresses": "Postfach 456, 50668 Köln", + "termination_date": "2025-12-31", + "termination_reason": "Kundenkündigung", "billing_period": "monthly", "billing_duration_amount": 30, - "renewal_duration_amount": 365, - "renewal_duration_unit": "years", + "renewal_duration_amount": 12, + "renewal_duration_unit": "months", "notice_time_amount": 30, "notice_time_unit": "months", - "start_date": "2021-01-01", - "billing_due_day": 2, - "installment_amount": 10050, + "start_date": "2025-01-01", + "billing_due_day": 15, + "installment_amount": 8500, "balance": 8990, "balance_currency": "EUR" }' @@ -538,32 +490,32 @@ epilot billing createContractEntity --jsonata '$' ```json { "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", - "contract_name": "Grid Contract", - "contract_number": "12345", - "status": "approved", - "description": "This contract is for the supply of widgets.", - "account_number": "67890", + "_title": "Abschlagszahlung Juli 2025", + "_org": "123456", + "_schema": "billing_event", + "_tags": ["billing", "energy"], + "_created_at": "2025-06-15T10:30:00Z", + "_updated_at": "2025-06-15T14:45:00Z", + "contract_name": "Stromvertrag Haushalt", + "contract_number": "STR-2025-001234", + "status": "active", + "description": "Haushaltsstrom-Tarif mit 24 Monaten Preisgarantie", + "account_number": "KD-67890", "branch": "power", - "billing_address": "123 Main St, Anytown", - "delivery_address": "456 Elm St, Anytown", - "additional_addresses": "789 Oak St, Anytown", - "termination_date": "2022-01-01", - "termination_reason": "Non-payment", + "billing_address": "Musterstraße 123, 50667 Köln", + "delivery_address": "Musterstraße 123, 50667 Köln", + "additional_addresses": "Postfach 456, 50668 Köln", + "termination_date": "2025-12-31", + "termination_reason": "Kundenkündigung", "billing_period": "monthly", "billing_duration_amount": 30, - "renewal_duration_amount": 365, - "renewal_duration_unit": "years", + "renewal_duration_amount": 12, + "renewal_duration_unit": "months", "notice_time_amount": 30, "notice_time_unit": "months", - "start_date": "2021-01-01", - "billing_due_day": 2, - "installment_amount": 10050, + "start_date": "2025-01-01", + "billing_due_day": 15, + "installment_amount": 8500, "balance": 8990, "balance_currency": "EUR" } @@ -575,7 +527,7 @@ epilot billing createContractEntity --jsonata '$' ### `updateContractEntity` -Update an existing contract entity. +Update an existing contract entity (Vertrag). `PATCH /v1/billing/contracts/{id}` @@ -583,7 +535,7 @@ Update an existing contract entity. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `id` | path | string | Yes | ID of the contract entity to update | +| `id` | path | string | Yes | Unique identifier of the contract (Vertragsnummer) | **Request Body** (required) @@ -591,42 +543,42 @@ Update an existing contract entity. ```bash epilot billing updateContractEntity \ - -p id=123e4567-e89b-12d3-a456-426614174000 + -p id=f589786b-3024-43cd-9cb3-5a3c953f2896 ``` With request body: ```bash epilot billing updateContractEntity \ - -p id=123e4567-e89b-12d3-a456-426614174000 \ + -p id=f589786b-3024-43cd-9cb3-5a3c953f2896 \ -d '{ "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", - "contract_name": "Grid Contract", - "contract_number": "12345", - "status": "approved", - "description": "This contract is for the supply of widgets.", - "account_number": "67890", + "_title": "Abschlagszahlung Juli 2025", + "_org": "123456", + "_schema": "billing_event", + "_tags": ["billing", "energy"], + "_created_at": "2025-06-15T10:30:00Z", + "_updated_at": "2025-06-15T14:45:00Z", + "contract_name": "Stromvertrag Haushalt", + "contract_number": "STR-2025-001234", + "status": "active", + "description": "Haushaltsstrom-Tarif mit 24 Monaten Preisgarantie", + "account_number": "KD-67890", "branch": "power", - "billing_address": "123 Main St, Anytown", - "delivery_address": "456 Elm St, Anytown", - "additional_addresses": "789 Oak St, Anytown", - "termination_date": "2022-01-01", - "termination_reason": "Non-payment", + "billing_address": "Musterstraße 123, 50667 Köln", + "delivery_address": "Musterstraße 123, 50667 Köln", + "additional_addresses": "Postfach 456, 50668 Köln", + "termination_date": "2025-12-31", + "termination_reason": "Kundenkündigung", "billing_period": "monthly", "billing_duration_amount": 30, - "renewal_duration_amount": 365, - "renewal_duration_unit": "years", + "renewal_duration_amount": 12, + "renewal_duration_unit": "months", "notice_time_amount": 30, "notice_time_unit": "months", - "start_date": "2021-01-01", - "billing_due_day": 2, - "installment_amount": 10050, + "start_date": "2025-01-01", + "billing_due_day": 15, + "installment_amount": 8500, "balance": 8990, "balance_currency": "EUR" }' @@ -635,19 +587,19 @@ epilot billing updateContractEntity \ Using positional args for path parameters: ```bash -epilot billing updateContractEntity 123e4567-e89b-12d3-a456-426614174000 +epilot billing updateContractEntity f589786b-3024-43cd-9cb3-5a3c953f2896 ``` Using stdin pipe: ```bash -cat body.json | epilot billing updateContractEntity -p id=123e4567-e89b-12d3-a456-426614174000 +cat body.json | epilot billing updateContractEntity -p id=f589786b-3024-43cd-9cb3-5a3c953f2896 ``` With JSONata filter: ```bash -epilot billing updateContractEntity -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot billing updateContractEntity -p id=f589786b-3024-43cd-9cb3-5a3c953f2896 --jsonata '$' ```
@@ -656,32 +608,32 @@ epilot billing updateContractEntity -p id=123e4567-e89b-12d3-a456-426614174000 - ```json { "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", - "_title": "string", - "_org": "string", - "_schema": "contact", - "_tags": ["string"], - "_created_at": "string", - "_updated_at": "string", - "contract_name": "Grid Contract", - "contract_number": "12345", - "status": "approved", - "description": "This contract is for the supply of widgets.", - "account_number": "67890", + "_title": "Abschlagszahlung Juli 2025", + "_org": "123456", + "_schema": "billing_event", + "_tags": ["billing", "energy"], + "_created_at": "2025-06-15T10:30:00Z", + "_updated_at": "2025-06-15T14:45:00Z", + "contract_name": "Stromvertrag Haushalt", + "contract_number": "STR-2025-001234", + "status": "active", + "description": "Haushaltsstrom-Tarif mit 24 Monaten Preisgarantie", + "account_number": "KD-67890", "branch": "power", - "billing_address": "123 Main St, Anytown", - "delivery_address": "456 Elm St, Anytown", - "additional_addresses": "789 Oak St, Anytown", - "termination_date": "2022-01-01", - "termination_reason": "Non-payment", + "billing_address": "Musterstraße 123, 50667 Köln", + "delivery_address": "Musterstraße 123, 50667 Köln", + "additional_addresses": "Postfach 456, 50668 Köln", + "termination_date": "2025-12-31", + "termination_reason": "Kundenkündigung", "billing_period": "monthly", "billing_duration_amount": 30, - "renewal_duration_amount": 365, - "renewal_duration_unit": "years", + "renewal_duration_amount": 12, + "renewal_duration_unit": "months", "notice_time_amount": 30, "notice_time_unit": "months", - "start_date": "2021-01-01", - "billing_due_day": 2, - "installment_amount": 10050, + "start_date": "2025-01-01", + "billing_due_day": 15, + "installment_amount": 8500, "balance": 8990, "balance_currency": "EUR" } @@ -693,7 +645,7 @@ epilot billing updateContractEntity -p id=123e4567-e89b-12d3-a456-426614174000 - ### `deleteContractEntity` -Delete an existing contract entity. +Delete an existing contract entity (Vertrag). `DELETE /v1/billing/contracts/{id}` @@ -701,32 +653,363 @@ Delete an existing contract entity. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `id` | path | string | Yes | ID of the contract entity to delete | +| `id` | path | string | Yes | Unique identifier of the contract to delete | **Sample Call** ```bash epilot billing deleteContractEntity \ + -p id=f589786b-3024-43cd-9cb3-5a3c953f2896 +``` + +Using positional args for path parameters: + +```bash +epilot billing deleteContractEntity f589786b-3024-43cd-9cb3-5a3c953f2896 +``` + +With JSONata filter: + +```bash +epilot billing deleteContractEntity -p id=f589786b-3024-43cd-9cb3-5a3c953f2896 --jsonata '$' +``` + +--- + +### `getContractPricingInformation` + +Get current pricing information and recent configuration history for a Contract. + +`GET /v1/billing/contracts/{id}/pricing_information` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | ID of the Contract entity | +| `history_change_types` | query | string | No | Comma-separated billing configuration change types to return. When omitted, installment history is returned by default. | +| `include_history` | query | boolean | No | Include recent configuration history in the pricing information response. Set to false when using the dedicated configuration history endpoint. | + +**Sample Call** + +```bash +epilot billing getContractPricingInformation \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot billing getContractPricingInformation 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot billing getContractPricingInformation -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'entity_type' +``` + +
+Sample Response + +```json +{ + "entity_type": "contract", + "entity_id": "string", + "title": "string", + "current_installment_amount": { + "amount": 10050, + "amount_decimal": "100.50", + "currency": "EUR" + }, + "context": { + "base_price": { + "price_id": "string", + "price_title": "string", + "tariff_type": "string", + "pricing_model": "string", + "unit_amount_gross_decimal": "string", + "unit_amount_net_decimal": "string", + "before_discount_unit_amount_gross_decimal": "string", + "before_discount_unit_amount_net_decimal": "string", + "unit_discount_amount_decimal": "string", + "unit_discount_amount_net_decimal": "string", + "currency": "EUR", + "billing_period": "string", + "unit": "string", + "has_discount": true, + "is_dynamic_tariff": true, + "dynamic_tariff": {} + }, + "base_prices": [ + {} + ], + "working_price": { + "price_id": "string", + "price_title": "string", + "tariff_type": "string", + "pricing_model": "string", + "unit_amount_gross_decimal": "string", + "unit_amount_net_decimal": "string", + "before_discount_unit_amount_gross_decimal": "string", + "before_discount_unit_amount_net_decimal": "string", + "unit_discount_amount_decimal": "string", + "unit_discount_amount_net_decimal": "string", + "currency": "EUR", + "billing_period": "string", + "unit": "string", + "has_discount": true, + "is_dynamic_tariff": true, + "dynamic_tariff": {} + }, + "working_prices": [ + {} + ] + }, + "balance": { + "amount": 8990, + "amount_decimal": "89.90", + "currency": "EUR" + }, + "schedule": { + "billing_due_day": 0, + "billing_period": "weekly", + "installments_per_year": 0, + "inferred": true + }, + "pending_installment_change": true, + "history": [ + { + "event_id": "string", + "org_id": "string", + "entity_type": "contract", + "entity_id": "string", + "schema_version": 1, + "effective_at": "1970-01-01T00:00:00.000Z", + "changed_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "source": "portal", + "source_label": "string", + "source_system": "string", + "source_reference": "string", + "change_type": "installment_amount_changed", + "previous_value": {}, + "new_value": {}, + "context": {} + } + ] +} +``` + +
+ +--- + +### `getBillingAccountPricingInformation` + +Get current pricing information for the active Contracts linked to a Billing Account. + +`GET /v1/billing/billing_accounts/{id}/pricing_information` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | ID of the Billing Account entity | +| `history_change_types` | query | string | No | Comma-separated billing configuration change types to return. When omitted, installment history is returned by default. | +| `include_history` | query | boolean | No | Include recent configuration history in the pricing information response. Set to false when using the dedicated configuration history endpoint. | + +**Sample Call** + +```bash +epilot billing getBillingAccountPricingInformation \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot billing getBillingAccountPricingInformation 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot billing getBillingAccountPricingInformation -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'entity_type' +``` + +
+Sample Response + +```json +{ + "entity_type": "billing_account", + "entity_id": "string", + "title": "string", + "balance": { + "amount": 8990, + "amount_decimal": "89.90", + "currency": "EUR" + }, + "contracts": [ + { + "entity_type": "contract", + "entity_id": "string", + "title": "string", + "current_installment_amount": {}, + "context": {}, + "balance": {}, + "schedule": {}, + "pending_installment_change": true, + "history": [] + } + ] +} +``` + +
+ +--- + +### `getContractConfigurationHistory` + +Get billing configuration history for a Contract. + +`GET /v1/billing/contracts/{id}/configuration_history` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | ID of the Contract entity | +| `change_type` | query | "installment_amount_changed" \| "contract_pricing_changed" | No | Billing configuration change type to return. | +| `history_change_types` | query | string | No | Comma-separated billing configuration change types to return. When omitted, installment history is returned by default. | +| `from` | query | number | No | Initial offset for paginated results. | +| `size` | query | number | No | Maximum number of results to return. | + +**Sample Call** + +```bash +epilot billing getContractConfigurationHistory \ -p id=123e4567-e89b-12d3-a456-426614174000 ``` Using positional args for path parameters: ```bash -epilot billing deleteContractEntity 123e4567-e89b-12d3-a456-426614174000 +epilot billing getContractConfigurationHistory 123e4567-e89b-12d3-a456-426614174000 ``` With JSONata filter: ```bash -epilot billing deleteContractEntity -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot billing getContractConfigurationHistory -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'history' +``` + +
+Sample Response + +```json +{ + "history": [ + { + "event_id": "string", + "org_id": "string", + "entity_type": "contract", + "entity_id": "string", + "schema_version": 1, + "effective_at": "1970-01-01T00:00:00.000Z", + "changed_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "source": "portal", + "source_label": "string", + "source_system": "string", + "source_reference": "string", + "change_type": "installment_amount_changed", + "previous_value": {}, + "new_value": {}, + "context": {} + } + ], + "total": 0 +} ``` +
+ +--- + +### `getBillingAccountConfigurationHistory` + +Get merged billing configuration history for active Contracts linked to a Billing Account. + +`GET /v1/billing/billing_accounts/{id}/configuration_history` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string | Yes | ID of the Billing Account entity | +| `change_type` | query | "installment_amount_changed" \| "contract_pricing_changed" | No | Billing configuration change type to return. | +| `history_change_types` | query | string | No | Comma-separated billing configuration change types to return. When omitted, installment history is returned by default. | +| `from` | query | number | No | Initial offset for paginated results. | +| `size` | query | number | No | Maximum number of results to return. | + +**Sample Call** + +```bash +epilot billing getBillingAccountConfigurationHistory \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot billing getBillingAccountConfigurationHistory 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot billing getBillingAccountConfigurationHistory -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'history' +``` + +
+Sample Response + +```json +{ + "history": [ + { + "event_id": "string", + "org_id": "string", + "entity_type": "contract", + "entity_id": "string", + "schema_version": 1, + "effective_at": "1970-01-01T00:00:00.000Z", + "changed_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "source": "portal", + "source_label": "string", + "source_system": "string", + "source_reference": "string", + "change_type": "installment_amount_changed", + "previous_value": {}, + "new_value": {}, + "context": {} + } + ], + "total": 0 +} +``` + +
+ --- ### `getCustomerBalance` -Get total balance across all contracts and orders of a customer entity. +Retrieve the total balance (Kontostand) across all contracts and orders for a customer. `GET /v1/billing/customers/{id}/balance` @@ -734,7 +1017,9 @@ Get total balance across all contracts and orders of a customer entity. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `id` | path | string | Yes | Customer entity ID (contact or account) | +| `id` | path | string | Yes | Customer entity ID. This can be either a contact ID (Kontakt-ID) or +an account ID (Kundenkonto-ID). + | **Sample Call** diff --git a/packages/cli/docs/blueprint-manifest.md b/packages/cli/docs/blueprint-manifest.md index 5bffbb029..ef41d02d0 100644 --- a/packages/cli/docs/blueprint-manifest.md +++ b/packages/cli/docs/blueprint-manifest.md @@ -42,15 +42,15 @@ epilot blueprint-manifest getJob -p job_id=4854bb2a-94f9-424d-a968-3fb17fb0bf89 - [`listBlueprints`](#listblueprints) — List Custom and Installed Blueprints - [`createBlueprint`](#createblueprint) — Create a Blueprint - [`listInstalledMarketplaceBlueprints`](#listinstalledmarketplaceblueprints) — List installed Marketplace Blueprints for the organization. -- [`preInstallBlueprint`](#preinstallblueprint) — Pre-install a Blueprint based on a blueprint file +- [`preInstallBlueprint`](#preinstallblueprint) — Pre-install a Blueprint based on a blueprint file. Format-agnostic: the engine is detected from the uploaded archive, so - [`getBlueprintPreview`](#getblueprintpreview) — Get Blueprint Preview by ID -- [`installBlueprint`](#installblueprint) — Kick off a new blueprint installation job. Returns 202 Accepted with Location header pointing to the job resource - [`getBlueprint`](#getblueprint) — Get Blueprint by ID - [`updateBlueprint`](#updateblueprint) — Update a Blueprint - [`deleteBlueprint`](#deleteblueprint) — Delete a Blueprint -- [`validateBlueprint`](#validateblueprint) — Start a blueprint validation job. Validates Terraform for the blueprint (all types). +- [`addBlueprintNote`](#addblueprintnote) — Append an internal note to a blueprint. `id`, `created_at` and `created_by` +- [`updateBlueprintNote`](#updateblueprintnote) — Rewrite the text of an existing internal note. The note keeps its position in +- [`deleteBlueprintNote`](#deleteblueprintnote) — Remove a single internal note from a blueprint. - [`verifyBlueprint`](#verifyblueprint) — Start a blueprint verification job. Compares resource configurations between a source org -- [`exportBlueprint`](#exportblueprint) — Kick off a new blueprint export job. Returns 202 Accepted with Location header pointing to the job resource. - [`listMarketplaceSlugs`](#listmarketplaceslugs) — List all available marketplace blueprint slugs from Webflow CMS. - [`publishBlueprint`](#publishblueprint) — Publish a blueprint to the marketplace. Exports the blueprint, uploads it to file-api with public access, and updates th - [`formatBlueprintDescription`](#formatblueprintdescription) — Format a blueprint description as markdown using AI. @@ -62,23 +62,24 @@ epilot blueprint-manifest getJob -p job_id=4854bb2a-94f9-424d-a968-3fb17fb0bf89 - [`bulkDeleteBlueprintResources`](#bulkdeleteblueprintresources) — Bulk delete resources in a Blueprint - [`updateBlueprintResource`](#updateblueprintresource) — Update a resource in a Blueprint - [`deleteBlueprintResource`](#deleteblueprintresource) — Delete a resource from a Blueprint -- [`installBlueprintV3`](#installblueprintv3) — Install a blueprint using the V3 engine (direct API calls, no Terraform). +- [`publishBlueprintV3`](#publishblueprintv3) — Starts an asynchronous V3 publication. The result is a signed, portable package; poll the existing blueprint job endpoin +- [`preInstallBlueprintV3`](#preinstallblueprintv3) — Validates a signed V3 package and returns the destination-specific resource plan used by the install UI. +- [`installBlueprintV3`](#installblueprintv3) — Install a blueprint into a single destination org using the V3 engine (direct API - [`restoreBlueprintDeploymentV3`](#restoreblueprintdeploymentv3) — Roll a deployment back to its pre-install state. Two phases: - [`getRestorePreview`](#getrestorepreview) — Computes what would happen if the user triggered a restore on this +- [`triggerDeploymentHealthCheckV3`](#triggerdeploymenthealthcheckv3) — Starts a read-only health scan of the resources this deployment's +- [`getDeploymentHealthReportV3`](#getdeploymenthealthreportv3) — Returns the most recent health report produced for this deployment - [`getBlueprintLineageV3`](#getblueprintlineagev3) — Returns the lineage registry entries for a blueprint's resources in the current org. - -**Patches** -- [`detectPatchChanges`](#detectpatchchanges) — Detect changes between the current state of a blueprint's resources and its tfstate baseline. -- [`listPatches`](#listpatches) — List all patches for a blueprint. -- [`createPatch`](#createpatch) — Create a new patch for a blueprint. -- [`getPatch`](#getpatch) — Get a patch by ID, including per-org execution results. -- [`applyPatch`](#applypatch) — Apply a patch to a single destination org. -- [`retryPatchOrg`](#retrypatchorg) — Retry a failed patch execution for a specific org. +- [`createBulkInstallV3`](#createbulkinstallv3) — Install one source blueprint into many destination organizations in a single +- [`getBulkInstallV3`](#getbulkinstallv3) — Returns the bulk install parent with aggregate status and counts. Scoped by the +- [`listBulkInstallTargetsV3`](#listbulkinstalltargetsv3) — Pages through the bulk install's target rows. Each row hydrates its latest child +- [`retryBulkInstallTargetV3`](#retrybulkinstalltargetv3) — Retries a single failed target. Allowed only for `FAILED` and `PARTIAL_SUCCESS` **Jobs** - [`listBlueprintJobs`](#listblueprintjobs) — List all blueprint jobs -- [`getBlueprintJob`](#getblueprintjob) — Poll current state of a job. -- [`continueInstallationJob`](#continueinstallationjob) — Continue an installation job if it is waiting for user action. +- [`getBlueprintJob`](#getblueprintjob) — Poll the current state of a job. Serves both Terraform (v2) and V3-engine jobs — +- [`continueInstallationJob`](#continueinstallationjob) — Resume an installation job that is paused at `status: "WAITING_USER_ACTION"` after +- [`retryInstallationJob`](#retryinstallationjob) — Retry a finished V3 installation job whose status is `FAILED` or - [`cancelBlueprintJob`](#cancelblueprintjob) — Cancel a blueprint job if it is still running. **Marketplace Listings** @@ -181,12 +182,15 @@ epilot blueprint-manifest listBlueprints --jsonata 'results[0]' "title": "string", "slug": "string", "description": {}, + "notes": [], "version": "string", "deployments": [], "is_verified": true, "latest_verification": {}, "ignored_resource_addresses": ["string"], "installation_status": "IN_PROGRESS", + "active_restore_job_id": "string", + "active_restore_started_at": "1970-01-01T00:00:00.000Z", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "created_by": {}, @@ -235,6 +239,15 @@ epilot blueprint-manifest createBlueprint \ "preinstall": "This is the content of the preinstall.md file which contains the blueprint description.\n", "postinstall": "This is the content of the postinstall.md file\n" }, + "notes": [ + { + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": {} + } + ], "version": "string", "deployments": [ { @@ -244,6 +257,7 @@ epilot blueprint-manifest createBlueprint \ "destination_blueprint_id": "string", "job_id": "string", "triggered_at": "1970-01-01T00:00:00.000Z", + "performed_by": {}, "note": "string", "status": "IN_PROGRESS", "restore_details": {}, @@ -261,6 +275,8 @@ epilot blueprint-manifest createBlueprint \ "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", "destination_org_id": "string", "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "installation_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "sync_engine": "terraform", "summary": { "total_resources": 0, "matched": 0, @@ -271,6 +287,8 @@ epilot blueprint-manifest createBlueprint \ }, "ignored_resource_addresses": ["string"], "installation_status": "IN_PROGRESS", + "active_restore_job_id": "string", + "active_restore_started_at": "1970-01-01T00:00:00.000Z", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "created_by": { @@ -340,6 +358,15 @@ epilot blueprint-manifest createBlueprint --jsonata '$' "preinstall": "This is the content of the preinstall.md file which contains the blueprint description.\n", "postinstall": "This is the content of the postinstall.md file\n" }, + "notes": [ + { + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": {} + } + ], "version": "string", "deployments": [ { @@ -349,6 +376,7 @@ epilot blueprint-manifest createBlueprint --jsonata '$' "destination_blueprint_id": "string", "job_id": "string", "triggered_at": "1970-01-01T00:00:00.000Z", + "performed_by": {}, "note": "string", "status": "IN_PROGRESS", "restore_details": {}, @@ -366,6 +394,8 @@ epilot blueprint-manifest createBlueprint --jsonata '$' "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", "destination_org_id": "string", "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "installation_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "sync_engine": "terraform", "summary": { "total_resources": 0, "matched": 0, @@ -376,6 +406,8 @@ epilot blueprint-manifest createBlueprint --jsonata '$' }, "ignored_resource_addresses": ["string"], "installation_status": "IN_PROGRESS", + "active_restore_job_id": "string", + "active_restore_started_at": "1970-01-01T00:00:00.000Z", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "created_by": { @@ -482,7 +514,7 @@ epilot blueprint-manifest listInstalledMarketplaceBlueprints --jsonata 'results[ ### `preInstallBlueprint` -Pre-install a Blueprint based on a blueprint file +Pre-install a Blueprint based on a blueprint file. Format-agnostic: the engine is detected from the uploaded archive, so `POST /v2/blueprint-manifest/blueprints:pre-install` @@ -521,6 +553,7 @@ epilot blueprint-manifest preInstallBlueprint --jsonata 'id' "version": "string", "slug": "string", "source_type": "marketplace", + "sync_engine": "terraform", "blueprint_file_s3_key": "string", "is_verified": true, "docs_url": "string", @@ -606,6 +639,7 @@ epilot blueprint-manifest getBlueprintPreview -p preview_id=123e4567-e89b-12d3-a "version": "string", "slug": "string", "source_type": "marketplace", + "sync_engine": "terraform", "blueprint_file_s3_key": "string", "is_verified": true, "docs_url": "string", @@ -646,56 +680,6 @@ epilot blueprint-manifest getBlueprintPreview -p preview_id=123e4567-e89b-12d3-a --- -### `installBlueprint` - -Kick off a new blueprint installation job. Returns 202 Accepted with Location header pointing to the job resource - -`POST /v2/blueprint-manifest/blueprint:install` - -**Request Body** (required) - -**Sample Call** - -```bash -epilot blueprint-manifest installBlueprint -``` - -With request body: - -```bash -epilot blueprint-manifest installBlueprint \ - -d '{ - "source_org_id": "string", - "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "source_blueprint_file": "string", - "destination_org_id": "string", - "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "source_auth_token": "string", - "destination_auth_token": "string", - "options": { - "resources_to_ignore": ["string"] - }, - "mode": "simple", - "source_blueprint_type": "marketplace", - "slug": "string", - "auto_enable_features": true -}' -``` - -Using stdin pipe: - -```bash -cat body.json | epilot blueprint-manifest installBlueprint -``` - -With JSONata filter: - -```bash -epilot blueprint-manifest installBlueprint --jsonata '$' -``` - ---- - ### `getBlueprint` Get Blueprint by ID @@ -739,6 +723,15 @@ epilot blueprint-manifest getBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1 "preinstall": "This is the content of the preinstall.md file which contains the blueprint description.\n", "postinstall": "This is the content of the postinstall.md file\n" }, + "notes": [ + { + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": {} + } + ], "version": "string", "deployments": [ { @@ -748,6 +741,7 @@ epilot blueprint-manifest getBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1 "destination_blueprint_id": "string", "job_id": "string", "triggered_at": "1970-01-01T00:00:00.000Z", + "performed_by": {}, "note": "string", "status": "IN_PROGRESS", "restore_details": {}, @@ -765,6 +759,8 @@ epilot blueprint-manifest getBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1 "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", "destination_org_id": "string", "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "installation_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "sync_engine": "terraform", "summary": { "total_resources": 0, "matched": 0, @@ -775,6 +771,8 @@ epilot blueprint-manifest getBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1 }, "ignored_resource_addresses": ["string"], "installation_status": "IN_PROGRESS", + "active_restore_job_id": "string", + "active_restore_started_at": "1970-01-01T00:00:00.000Z", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "created_by": { @@ -858,6 +856,15 @@ epilot blueprint-manifest updateBlueprint \ "preinstall": "This is the content of the preinstall.md file which contains the blueprint description.\n", "postinstall": "This is the content of the postinstall.md file\n" }, + "notes": [ + { + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": {} + } + ], "version": "string", "deployments": [ { @@ -867,6 +874,7 @@ epilot blueprint-manifest updateBlueprint \ "destination_blueprint_id": "string", "job_id": "string", "triggered_at": "1970-01-01T00:00:00.000Z", + "performed_by": {}, "note": "string", "status": "IN_PROGRESS", "restore_details": {}, @@ -884,6 +892,8 @@ epilot blueprint-manifest updateBlueprint \ "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", "destination_org_id": "string", "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "installation_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "sync_engine": "terraform", "summary": { "total_resources": 0, "matched": 0, @@ -894,6 +904,8 @@ epilot blueprint-manifest updateBlueprint \ }, "ignored_resource_addresses": ["string"], "installation_status": "IN_PROGRESS", + "active_restore_job_id": "string", + "active_restore_started_at": "1970-01-01T00:00:00.000Z", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "created_by": { @@ -969,6 +981,15 @@ epilot blueprint-manifest updateBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 "preinstall": "This is the content of the preinstall.md file which contains the blueprint description.\n", "postinstall": "This is the content of the postinstall.md file\n" }, + "notes": [ + { + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": {} + } + ], "version": "string", "deployments": [ { @@ -978,6 +999,7 @@ epilot blueprint-manifest updateBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 "destination_blueprint_id": "string", "job_id": "string", "triggered_at": "1970-01-01T00:00:00.000Z", + "performed_by": {}, "note": "string", "status": "IN_PROGRESS", "restore_details": {}, @@ -995,6 +1017,8 @@ epilot blueprint-manifest updateBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", "destination_org_id": "string", "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "installation_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "sync_engine": "terraform", "summary": { "total_resources": 0, "matched": 0, @@ -1005,6 +1029,8 @@ epilot blueprint-manifest updateBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 }, "ignored_resource_addresses": ["string"], "installation_status": "IN_PROGRESS", + "active_restore_job_id": "string", + "active_restore_started_at": "1970-01-01T00:00:00.000Z", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "created_by": { @@ -1097,6 +1123,15 @@ epilot blueprint-manifest deleteBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 "preinstall": "This is the content of the preinstall.md file which contains the blueprint description.\n", "postinstall": "This is the content of the postinstall.md file\n" }, + "notes": [ + { + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": {} + } + ], "version": "string", "deployments": [ { @@ -1106,6 +1141,7 @@ epilot blueprint-manifest deleteBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 "destination_blueprint_id": "string", "job_id": "string", "triggered_at": "1970-01-01T00:00:00.000Z", + "performed_by": {}, "note": "string", "status": "IN_PROGRESS", "restore_details": {}, @@ -1123,6 +1159,8 @@ epilot blueprint-manifest deleteBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", "destination_org_id": "string", "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "installation_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "sync_engine": "terraform", "summary": { "total_resources": 0, "matched": 0, @@ -1133,6 +1171,8 @@ epilot blueprint-manifest deleteBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 }, "ignored_resource_addresses": ["string"], "installation_status": "IN_PROGRESS", + "active_restore_job_id": "string", + "active_restore_started_at": "1970-01-01T00:00:00.000Z", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "created_by": { @@ -1182,44 +1222,11 @@ epilot blueprint-manifest deleteBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6 --- -### `validateBlueprint` - -Start a blueprint validation job. Validates Terraform for the blueprint (all types). - -`POST /v2/blueprint-manifest/blueprints/{blueprint_id}/validate` - -**Parameters** - -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `blueprint_id` | path | string | Yes | | - -**Sample Call** - -```bash -epilot blueprint-manifest validateBlueprint \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -``` - -Using positional args for path parameters: - -```bash -epilot blueprint-manifest validateBlueprint c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -``` - -With JSONata filter: - -```bash -epilot blueprint-manifest validateBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata '$' -``` - ---- - -### `verifyBlueprint` +### `addBlueprintNote` -Start a blueprint verification job. Compares resource configurations between a source org +Append an internal note to a blueprint. `id`, `created_at` and `created_by` -`POST /v2/blueprint-manifest/blueprints/{blueprint_id}:verify` +`POST /v2/blueprint-manifest/blueprints/{blueprint_id}/notes` **Parameters** @@ -1232,83 +1239,91 @@ Start a blueprint verification job. Compares resource configurations between a s **Sample Call** ```bash -epilot blueprint-manifest verifyBlueprint \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -``` - -With request body: - -```bash -epilot blueprint-manifest verifyBlueprint \ +epilot blueprint-manifest addBlueprintNote \ -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -d '{ - "source_org_id": "string", - "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "destination_org_id": "string", - "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "source_auth_token": "string", - "destination_auth_token": "string" -}' + -d '{"text":"string"}' ``` Using positional args for path parameters: ```bash -epilot blueprint-manifest verifyBlueprint c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +epilot blueprint-manifest addBlueprintNote c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 ``` Using stdin pipe: ```bash -cat body.json | epilot blueprint-manifest verifyBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +cat body.json | epilot blueprint-manifest addBlueprintNote -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 ``` With JSONata filter: ```bash -epilot blueprint-manifest verifyBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata '$' +epilot blueprint-manifest addBlueprintNote -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": { + "name": "manifest@epilot.cloud", + "org_id": "911690", + "user_id": "11001045", + "token_id": "api_5ZugdRXasLfWBypHi93Fk" + } +} ``` +
+ --- -### `detectPatchChanges` +### `updateBlueprintNote` -Detect changes between the current state of a blueprint's resources and its tfstate baseline. +Rewrite the text of an existing internal note. The note keeps its position in -`POST /v2/blueprint-manifest/blueprints/{blueprint_id}/patches:detect` +`PATCH /v2/blueprint-manifest/blueprints/{blueprint_id}/notes/{note_id}` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `blueprint_id` | path | string | Yes | | +| `note_id` | path | string | Yes | | -**Request Body** +**Request Body** (required) **Sample Call** ```bash -epilot blueprint-manifest detectPatchChanges \ +epilot blueprint-manifest updateBlueprintNote \ -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -d '{"source_org_id":"string","dest_org_id":"string","dest_blueprint_id":"string","rollout_id":"string"}' + -p note_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"text":"string"}' ``` Using positional args for path parameters: ```bash -epilot blueprint-manifest detectPatchChanges c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +epilot blueprint-manifest updateBlueprintNote c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 123e4567-e89b-12d3-a456-426614174000 ``` Using stdin pipe: ```bash -cat body.json | epilot blueprint-manifest detectPatchChanges -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +cat body.json | epilot blueprint-manifest updateBlueprintNote -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p note_id=123e4567-e89b-12d3-a456-426614174000 ``` With JSONata filter: ```bash -epilot blueprint-manifest detectPatchChanges -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata 'resources' +epilot blueprint-manifest updateBlueprintNote -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p note_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' ```
@@ -1316,22 +1331,16 @@ epilot blueprint-manifest detectPatchChanges -p blueprint_id=c2d6cac8-bdd5-4ea2- ```json { - "resources": [ - { - "type": "string", - "source_id": "string", - "address": "string", - "name": "string", - "changes": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] - } - ] + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": { + "name": "manifest@epilot.cloud", + "org_id": "911690", + "user_id": "11001045", + "token_id": "api_5ZugdRXasLfWBypHi93Fk" + } } ``` @@ -1339,35 +1348,37 @@ epilot blueprint-manifest detectPatchChanges -p blueprint_id=c2d6cac8-bdd5-4ea2- --- -### `listPatches` +### `deleteBlueprintNote` -List all patches for a blueprint. +Remove a single internal note from a blueprint. -`GET /v2/blueprint-manifest/blueprints/{blueprint_id}/patches` +`DELETE /v2/blueprint-manifest/blueprints/{blueprint_id}/notes/{note_id}` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `blueprint_id` | path | string | Yes | | +| `note_id` | path | string | Yes | | **Sample Call** ```bash -epilot blueprint-manifest listPatches \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +epilot blueprint-manifest deleteBlueprintNote \ + -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ + -p note_id=123e4567-e89b-12d3-a456-426614174000 ``` Using positional args for path parameters: ```bash -epilot blueprint-manifest listPatches c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +epilot blueprint-manifest deleteBlueprintNote c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 123e4567-e89b-12d3-a456-426614174000 ``` With JSONata filter: ```bash -epilot blueprint-manifest listPatches -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata 'results[0]' +epilot blueprint-manifest deleteBlueprintNote -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p note_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'notes' ```
@@ -1375,37 +1386,18 @@ epilot blueprint-manifest listPatches -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1c ```json { - "total": 0, - "results": [ + "notes": [ { - "patch_id": "string", - "version": 0, - "blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "rollout_id": "string", - "source_org_id": "string", - "name": "string", - "description": "string", - "status": "draft", - "resources": [ - { - "type": "string", - "source_id": "string", - "address": "string", - "name": "string", - "changes": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] - } - ], - "changelog": "string", - "created_by": "string", + "id": "3f1c9b0e-2f3a-4a1f-9a3e-6f2b8c7d1e40", + "text": "Adjusted the meter-reading journey for the §14a rollout.", "created_at": "1970-01-01T00:00:00.000Z", - "applied_at": "1970-01-01T00:00:00.000Z" + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": { + "name": "manifest@epilot.cloud", + "org_id": "911690", + "user_id": "11001045", + "token_id": "api_5ZugdRXasLfWBypHi93Fk" + } } ] } @@ -1415,11 +1407,11 @@ epilot blueprint-manifest listPatches -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1c --- -### `createPatch` +### `verifyBlueprint` -Create a new patch for a blueprint. +Start a blueprint verification job. Compares resource configurations between a source org -`POST /v2/blueprint-manifest/blueprints/{blueprint_id}/patches` +`POST /v2/blueprint-manifest/blueprints/{blueprint_id}:verify` **Parameters** @@ -1432,130 +1424,63 @@ Create a new patch for a blueprint. **Sample Call** ```bash -epilot blueprint-manifest createPatch \ +epilot blueprint-manifest verifyBlueprint \ -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 ``` With request body: ```bash -epilot blueprint-manifest createPatch \ +epilot blueprint-manifest verifyBlueprint \ -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ -d '{ - "blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "rollout_id": "string", "source_org_id": "string", - "name": "string", - "description": "string", - "resources": [ - { - "type": "string", - "source_id": "string", - "address": "string", - "name": "string", - "changes": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] - } - ], - "changelog": "string" + "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "destination_org_id": "string", + "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "source_auth_token": "string", + "destination_auth_token": "string", + "installation_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "sync_engine": "terraform" }' ``` Using positional args for path parameters: ```bash -epilot blueprint-manifest createPatch c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +epilot blueprint-manifest verifyBlueprint c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 ``` Using stdin pipe: ```bash -cat body.json | epilot blueprint-manifest createPatch -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +cat body.json | epilot blueprint-manifest verifyBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 ``` With JSONata filter: ```bash -epilot blueprint-manifest createPatch -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata 'patch_id' -``` - -
-Sample Response - -```json -{ - "patch_id": "string", - "version": 0, - "blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "rollout_id": "string", - "source_org_id": "string", - "name": "string", - "description": "string", - "status": "draft", - "resources": [ - { - "type": "string", - "source_id": "string", - "address": "string", - "name": "string", - "changes": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] - } - ], - "changelog": "string", - "created_by": "string", - "created_at": "1970-01-01T00:00:00.000Z", - "applied_at": "1970-01-01T00:00:00.000Z" -} +epilot blueprint-manifest verifyBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata '$' ``` -
- --- -### `getPatch` - -Get a patch by ID, including per-org execution results. - -`GET /v2/blueprint-manifest/blueprints/{blueprint_id}/patches/{patch_id}` +### `listMarketplaceSlugs` -**Parameters** +List all available marketplace blueprint slugs from Webflow CMS. -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `blueprint_id` | path | string | Yes | | -| `patch_id` | path | string | Yes | | +`GET /v2/blueprint-manifest/marketplace/slugs` **Sample Call** ```bash -epilot blueprint-manifest getPatch \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -p patch_id=123e4567-e89b-12d3-a456-426614174000 -``` - -Using positional args for path parameters: - -```bash -epilot blueprint-manifest getPatch c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 123e4567-e89b-12d3-a456-426614174000 +epilot blueprint-manifest listMarketplaceSlugs ``` With JSONata filter: ```bash -epilot blueprint-manifest getPatch -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p patch_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot blueprint-manifest listMarketplaceSlugs --jsonata 'results[0]' ```
@@ -1563,286 +1488,7 @@ epilot blueprint-manifest getPatch -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdb ```json { - "patch_id": "string", - "version": 0, - "blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", - "rollout_id": "string", - "source_org_id": "string", - "name": "string", - "description": "string", - "status": "draft", - "resources": [ - { - "type": "string", - "source_id": "string", - "address": "string", - "name": "string", - "changes": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] - } - ], - "changelog": "string", - "created_by": "string", - "created_at": "1970-01-01T00:00:00.000Z", - "applied_at": "1970-01-01T00:00:00.000Z", - "org_results": [ - { - "patch_id": "string", - "version": 0, - "org_id": "string", - "org_name": "string", - "dest_blueprint_id": "string", - "status": "pending", - "error": "string", - "applied_at": "1970-01-01T00:00:00.000Z", - "retries": 0, - "changes_applied": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] - } - ] -} -``` - -
- ---- - -### `applyPatch` - -Apply a patch to a single destination org. - -`POST /v2/blueprint-manifest/blueprints/{blueprint_id}/patches/{patch_id}:apply` - -**Parameters** - -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `blueprint_id` | path | string | Yes | | -| `patch_id` | path | string | Yes | | - -**Request Body** (required) - -**Sample Call** - -```bash -epilot blueprint-manifest applyPatch \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -p patch_id=123e4567-e89b-12d3-a456-426614174000 -``` - -With request body: - -```bash -epilot blueprint-manifest applyPatch \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -p patch_id=123e4567-e89b-12d3-a456-426614174000 \ - -d '{ - "org_id": "string", - "org_name": "string", - "dest_blueprint_id": "string", - "dest_org_id": "string", - "destination_auth_token": "string" -}' -``` - -Using positional args for path parameters: - -```bash -epilot blueprint-manifest applyPatch c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 123e4567-e89b-12d3-a456-426614174000 -``` - -Using stdin pipe: - -```bash -cat body.json | epilot blueprint-manifest applyPatch -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p patch_id=123e4567-e89b-12d3-a456-426614174000 -``` - -With JSONata filter: - -```bash -epilot blueprint-manifest applyPatch -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p patch_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'patch_id' -``` - -
-Sample Response - -```json -{ - "patch_id": "string", - "version": 0, - "org_id": "string", - "org_name": "string", - "dest_blueprint_id": "string", - "status": "pending", - "error": "string", - "applied_at": "1970-01-01T00:00:00.000Z", - "retries": 0, - "changes_applied": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] -} -``` - -
- ---- - -### `retryPatchOrg` - -Retry a failed patch execution for a specific org. - -`POST /v2/blueprint-manifest/blueprints/{blueprint_id}/patches/{patch_id}/orgs/{org_id}:retry` - -**Parameters** - -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `blueprint_id` | path | string | Yes | | -| `patch_id` | path | string | Yes | | -| `org_id` | path | string | Yes | | - -**Request Body** - -**Sample Call** - -```bash -epilot blueprint-manifest retryPatchOrg \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -p patch_id=123e4567-e89b-12d3-a456-426614174000 \ - -p org_id=123e4567-e89b-12d3-a456-426614174000 \ - -d '{"org_name":"string","dest_blueprint_id":"string","destination_auth_token":"string"}' -``` - -Using positional args for path parameters: - -```bash -epilot blueprint-manifest retryPatchOrg c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 -``` - -Using stdin pipe: - -```bash -cat body.json | epilot blueprint-manifest retryPatchOrg -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p patch_id=123e4567-e89b-12d3-a456-426614174000 -p org_id=123e4567-e89b-12d3-a456-426614174000 -``` - -With JSONata filter: - -```bash -epilot blueprint-manifest retryPatchOrg -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p patch_id=123e4567-e89b-12d3-a456-426614174000 -p org_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'patch_id' -``` - -
-Sample Response - -```json -{ - "patch_id": "string", - "version": 0, - "org_id": "string", - "org_name": "string", - "dest_blueprint_id": "string", - "status": "pending", - "error": "string", - "applied_at": "1970-01-01T00:00:00.000Z", - "retries": 0, - "changes_applied": [ - { - "path": "string", - "op": "changed", - "baseline_value": {}, - "current_value": {} - } - ] -} -``` - -
- ---- - -### `exportBlueprint` - -Kick off a new blueprint export job. Returns 202 Accepted with Location header pointing to the job resource. - -`POST /v2/blueprint-manifest/blueprints/{blueprint_id}:export` - -**Parameters** - -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `blueprint_id` | path | string | Yes | | - -**Request Body** - -**Sample Call** - -```bash -epilot blueprint-manifest exportBlueprint \ - -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -d '{"destination_org_id":"string","destination_blueprint_id":"string","validate":true}' -``` - -Using positional args for path parameters: - -```bash -epilot blueprint-manifest exportBlueprint c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -``` - -Using stdin pipe: - -```bash -cat body.json | epilot blueprint-manifest exportBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -``` - -With JSONata filter: - -```bash -epilot blueprint-manifest exportBlueprint -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata '$' -``` - ---- - -### `listMarketplaceSlugs` - -List all available marketplace blueprint slugs from Webflow CMS. - -`GET /v2/blueprint-manifest/marketplace/slugs` - -**Sample Call** - -```bash -epilot blueprint-manifest listMarketplaceSlugs -``` - -With JSONata filter: - -```bash -epilot blueprint-manifest listMarketplaceSlugs --jsonata 'results[0]' -``` - -
-Sample Response - -```json -{ - "results": [ + "results": [ { "slug": "wallbox_b2c", "marketplace_slug": "wallbox-b2c", @@ -2082,7 +1728,34 @@ epilot blueprint-manifest addBlueprintResource -p blueprint_id=c2d6cac8-bdd5-4ea "impact_on_install": ["create"], "impact_on_install_reason": ["string"] } - ] + ], + "skipped": [ + { + "id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "type": "designbuilder", + "reason": "not_found" + } + ], + "errors": [ + { + "error": "string", + "code": "dependency_extraction", + "data": { + "formattedResource": { + "id": "string", + "name": "string", + "type": "string" + }, + "resource": "string", + "resourceDependency": "string", + "resources": ["string"], + "addresses": ["string"], + "originalError": "string" + } + } + ], + "total_errors": 0, + "errors_truncated": true } ``` @@ -2101,6 +1774,10 @@ Sync dependencies of all root resources in a Blueprint | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `blueprint_id` | path | string | Yes | | +| `trigger` | query | "manual" \| "pre_sync" \| "post_revert" | No | What initiated the sync. Automated triggers (`pre_sync`, +`post_revert`) are side effects of an operation the activity +feed already shows on its own row, so they are excluded from +the audit log — same | **Sample Call** @@ -2208,7 +1885,34 @@ epilot blueprint-manifest bulkAddBlueprintResources -p blueprint_id=c2d6cac8-bdd "impact_on_install": ["create"], "impact_on_install_reason": ["string"] } - ] + ], + "skipped": [ + { + "id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "type": "designbuilder", + "reason": "not_found" + } + ], + "errors": [ + { + "error": "string", + "code": "dependency_extraction", + "data": { + "formattedResource": { + "id": "string", + "name": "string", + "type": "string" + }, + "resource": "string", + "resourceDependency": "string", + "resources": ["string"], + "addresses": ["string"], + "originalError": "string" + } + } + ], + "total_errors": 0, + "errors_truncated": true } ``` @@ -2614,7 +2318,7 @@ epilot blueprint-manifest listBlueprintJobs --jsonata 'results[0]' ### `getBlueprintJob` -Poll current state of a job. +Poll the current state of a job. Serves both Terraform (v2) and V3-engine jobs — `GET /v2/blueprint-manifest/jobs/{job_id}` @@ -2702,7 +2406,7 @@ epilot blueprint-manifest getBlueprintJob -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbd ### `continueInstallationJob` -Continue an installation job if it is waiting for user action. +Resume an installation job that is paused at `status: "WAITING_USER_ACTION"` after `POST /v2/blueprint-manifest/jobs/{job_id}:continue` @@ -2719,7 +2423,7 @@ Continue an installation job if it is waiting for user action. ```bash epilot blueprint-manifest continueInstallationJob \ -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ - -d '{"resources_to_ignore":["string"]}' + -d '{"resources_to_ignore":["string"],"sync_notes":false,"source_auth_token":"string"}' ``` Using positional args for path parameters: @@ -2800,9 +2504,15 @@ epilot blueprint-manifest continueInstallationJob -p job_id=c2d6cac8-bdd5-4ea2-8 "name": "string", "status": "pending", "target_id": "string", - "error_message": "string" + "error_message": "string", + "error_code": "string", + "error_data": {} } ], + "options": { + "resources_to_ignore": ["string"], + "sync_notes": false + }, "status": "IN_PROGRESS" } ``` @@ -2811,6 +2521,39 @@ epilot blueprint-manifest continueInstallationJob -p job_id=c2d6cac8-bdd5-4ea2-8 --- +### `retryInstallationJob` + +Retry a finished V3 installation job whose status is `FAILED` or + +`POST /v2/blueprint-manifest/jobs/{job_id}:retry` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `job_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot blueprint-manifest retryInstallationJob \ + -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +Using positional args for path parameters: + +```bash +epilot blueprint-manifest retryInstallationJob c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest retryInstallationJob -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata '$' +``` + +--- + ### `cancelBlueprintJob` Cancel a blueprint job if it is still running. @@ -3679,13 +3422,137 @@ epilot blueprint-manifest publishMarketplaceListingVersion -p listing_id=123e456 --- -### `installBlueprintV3` +### `publishBlueprintV3` -Install a blueprint using the V3 engine (direct API calls, no Terraform). +Starts an asynchronous V3 publication. The result is a signed, portable package; poll the existing blueprint job endpoin -`POST /v3/blueprint-manifest/blueprint:install` +`POST /v3/blueprint-manifest/blueprints/{blueprint_id}:publish` -**Request Body** (required) +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `blueprint_id` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot blueprint-manifest publishBlueprintV3 \ + -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ + -d '{"publish_to_marketplace":false}' +``` + +Using positional args for path parameters: + +```bash +epilot blueprint-manifest publishBlueprintV3 c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot blueprint-manifest publishBlueprintV3 -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest publishBlueprintV3 -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata '$' +``` + +--- + +### `preInstallBlueprintV3` + +Validates a signed V3 package and returns the destination-specific resource plan used by the install UI. + +`POST /v3/blueprint-manifest/blueprints:pre-install` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot blueprint-manifest preInstallBlueprintV3 \ + -d '{"blueprint_file":"string","source_blueprint_type":"marketplace","slug":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot blueprint-manifest preInstallBlueprintV3 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest preInstallBlueprintV3 --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "string", + "org_id": "string", + "title": "string", + "description": { + "preinstall": "string" + }, + "version": "string", + "slug": "string", + "source_type": "marketplace", + "sync_engine": "terraform", + "blueprint_file_s3_key": "string", + "is_verified": true, + "docs_url": "string", + "recommended_apps": ["string"], + "required_features": { + "enabled": ["string"], + "disabled": ["string"] + }, + "created_at": "1970-01-01T00:00:00.000Z", + "created_by": { + "name": "manifest@epilot.cloud", + "org_id": "911690", + "user_id": "11001045", + "token_id": "api_5ZugdRXasLfWBypHi93Fk" + }, + "is_updating": true, + "resources": [ + { + "id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "name": "string", + "type": "designbuilder", + "address": "string", + "is_root": true, + "is_ready": true, + "is_hidden": true, + "is_disabled": false, + "hard_dependencies": ["designbuilder"], + "parent_resource_ids": ["c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341"], + "depends_on_addresses": ["string"], + "impact_on_install": ["create"], + "impact_on_install_reason": ["string"] + } + ] +} +``` + +
+ +--- + +### `installBlueprintV3` + +Install a blueprint into a single destination org using the V3 engine (direct API + +`POST /v3/blueprint-manifest/blueprint:install` + +**Request Body** (required) **Sample Call** @@ -3792,9 +3659,176 @@ epilot blueprint-manifest getRestorePreview -p blueprint_id=c2d6cac8-bdd5-4ea2-8 "reason": "modified", "last_synced_at": "1970-01-01T00:00:00.000Z", "current_updated_at": "1970-01-01T00:00:00.000Z", - "error_message": "string" + "error_message": "string", + "is_hidden": true, + "co_owned_by": [ + { + "blueprint_id": "string", + "title": "string" + } + ], + "protected_by": [ + { + "lineage_id": "string", + "type": "string", + "target_id": "string" + } + ] } - ] + ], + "has_effective_changes": true +} +``` + +
+ +--- + +### `triggerDeploymentHealthCheckV3` + +Starts a read-only health scan of the resources this deployment's + +`POST /v3/blueprint-manifest/blueprints/{blueprint_id}/deployments/{job_id}:health-check` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `blueprint_id` | path | string | Yes | | +| `job_id` | path | string | Yes | The install job whose deployment is being checked. | + +**Request Body** + +**Sample Call** + +```bash +epilot blueprint-manifest triggerDeploymentHealthCheckV3 \ + -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ + -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ + -d '{"source_org_id":"string","source_auth_token":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot blueprint-manifest triggerDeploymentHealthCheckV3 c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot blueprint-manifest triggerDeploymentHealthCheckV3 -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest triggerDeploymentHealthCheckV3 -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata '$' +``` + +--- + +### `getDeploymentHealthReportV3` + +Returns the most recent health report produced for this deployment + +`GET /v3/blueprint-manifest/blueprints/{blueprint_id}/deployments/{job_id}/health-report` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `blueprint_id` | path | string | Yes | | +| `job_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot blueprint-manifest getDeploymentHealthReportV3 \ + -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 \ + -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +Using positional args for path parameters: + +```bash +epilot blueprint-manifest getDeploymentHealthReportV3 c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest getDeploymentHealthReportV3 -p blueprint_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 -p job_id=c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341 --jsonata 'status' +``` + +
+Sample Response + +```json +{ + "status": "running", + "job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "blueprint_instance_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "destination_org_id": "string", + "generated_at": "1970-01-01T00:00:00.000Z", + "html_url": "string", + "summary": { + "resources_scanned": 0, + "unchecked": 0, + "errors": 0, + "warnings": 0, + "infos": 0 + }, + "findings": [ + { + "check_id": "live_readability", + "code": "missing_in_destination", + "severity": "error", + "resource_type": "string", + "lineage_id": "string", + "target_id": "string", + "resource_name": "string", + "message": "string", + "verdict": "string", + "evidence": { + "path": "string", + "referenced_id": "string", + "referenced_type": "string", + "referenced_name": "string", + "referenced_lineage_id": "string", + "expected_target_id": "string", + "reference_kind": "string", + "operation": "string", + "rejection_reason": "string", + "error_message": "string", + "response_status": 0 + } + } + ], + "coverage": { + "checks": [ + { + "check_id": "live_readability", + "status": "completed", + "scope": "tracked_resources", + "resources_considered": 0, + "details": ["string"] + } + ], + "resource_types": [ + { + "resource_type": "string", + "tracked": 0, + "readable_by_lineage": 0, + "missing_by_lineage": 0, + "read_errors": 0, + "unchecked": 0, + "specialized_checks": ["live_readability"], + "known_blind_spots": ["string"] + } + ] + }, + "error": "string" } ``` @@ -3856,6 +3890,306 @@ epilot blueprint-manifest getBlueprintLineageV3 -p blueprint_id=c2d6cac8-bdd5-4e --- +### `createBulkInstallV3` + +Install one source blueprint into many destination organizations in a single + +`POST /v3/blueprint-manifest/bulk-installs` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot blueprint-manifest createBulkInstallV3 +``` + +With request body: + +```bash +epilot blueprint-manifest createBulkInstallV3 \ + -d '{ + "source_org_id": "string", + "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "max_concurrency": 2, + "slug": "string", + "options": { + "resources_to_ignore": ["string"], + "sync_notes": false + }, + "targets": [ + { + "destination_org_id": "string", + "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "destination_auth_token": "string" + } + ] +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot blueprint-manifest createBulkInstallV3 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest createBulkInstallV3 --jsonata '$' +``` + +--- + +### `getBulkInstallV3` + +Returns the bulk install parent with aggregate status and counts. Scoped by the + +`GET /v3/blueprint-manifest/bulk-installs/{bulk_job_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `bulk_job_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot blueprint-manifest getBulkInstallV3 \ + -p bulk_job_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot blueprint-manifest getBulkInstallV3 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest getBulkInstallV3 -p bulk_job_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'bulk_job_id' +``` + +
+Sample Response + +```json +{ + "bulk_job_id": "string", + "source_org_id": "string", + "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "status": "QUEUED", + "target_count": 0, + "max_concurrency": 0, + "counts": { + "queued": 0, + "in_progress": 0, + "success": 0, + "partial_success": 0, + "failed": 0 + }, + "slug": "string", + "options": { + "resources_to_ignore": ["string"], + "sync_notes": false + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `listBulkInstallTargetsV3` + +Pages through the bulk install's target rows. Each row hydrates its latest child + +`GET /v3/blueprint-manifest/bulk-installs/{bulk_job_id}/targets` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `bulk_job_id` | path | string | Yes | | +| `limit` | query | number | No | | +| `cursor` | query | string | No | | + +**Sample Call** + +```bash +epilot blueprint-manifest listBulkInstallTargetsV3 \ + -p bulk_job_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot blueprint-manifest listBulkInstallTargetsV3 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest listBulkInstallTargetsV3 -p bulk_job_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "bulk_job_id": "string", + "destination_org_id": "string", + "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "status": "QUEUED", + "job_ids": ["c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341"], + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "job": {} + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `retryBulkInstallTargetV3` + +Retries a single failed target. Allowed only for `FAILED` and `PARTIAL_SUCCESS` + +`POST /v3/blueprint-manifest/bulk-installs/{bulk_job_id}/targets/{destination_org_id}:retry` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `bulk_job_id` | path | string | Yes | | +| `destination_org_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot blueprint-manifest retryBulkInstallTargetV3 \ + -p bulk_job_id=123e4567-e89b-12d3-a456-426614174000 \ + -p destination_org_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"destination_auth_token":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot blueprint-manifest retryBulkInstallTargetV3 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot blueprint-manifest retryBulkInstallTargetV3 -p bulk_job_id=123e4567-e89b-12d3-a456-426614174000 -p destination_org_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot blueprint-manifest retryBulkInstallTargetV3 -p bulk_job_id=123e4567-e89b-12d3-a456-426614174000 -p destination_org_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'bulk_job_id' +``` + +
+Sample Response + +```json +{ + "bulk_job_id": "string", + "destination_org_id": "string", + "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "status": "QUEUED", + "job_ids": ["c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341"], + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "job": { + "id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "events": [ + { + "timestamp": "1970-01-01T00:00:00.000Z", + "message": "string", + "errors": [ + { + "error": "string", + "code": "dependency_extraction", + "data": { + "formattedResource": { + "id": "string", + "name": "string", + "type": "string" + }, + "resource": "string", + "resourceDependency": "string", + "resources": ["string"], + "addresses": ["string"], + "originalError": "string" + } + } + ], + "level": "info", + "data": { + "installed_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "export_job_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "resources": 0 + } + } + ], + "triggered_at": "1970-01-01T00:00:00.000Z", + "created_by": { + "name": "manifest@epilot.cloud", + "org_id": "911690", + "user_id": "11001045", + "token_id": "api_5ZugdRXasLfWBypHi93Fk" + }, + "job_type": "install", + "source_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "source_blueprint_type": "custom", + "source_org_id": "string", + "source_blueprint_file": "string", + "destination_blueprint_id": "c2d6cac8-bdd5-4ea2-8a6c-1cbdbe77b341", + "destination_org_id": "string", + "slug": "string", + "sync_engine": "terraform", + "resource_progress": [ + { + "lineage_id": "string", + "type": "string", + "address": "string", + "name": "string", + "status": "pending", + "target_id": "string", + "error_message": "string", + "error_code": "string", + "error_data": {} + } + ], + "options": { + "resources_to_ignore": ["string"], + "sync_notes": false + }, + "status": "IN_PROGRESS" + } +} +``` + +
+ +--- + ### `listUniquenessCriteria` List all custom uniqueness criteria configured for the caller's organization. @@ -3884,10 +4218,13 @@ epilot blueprint-manifest listUniquenessCriteria --jsonata 'results[0]' "org_id": "string", "resource_type": "emailtemplate", "fields": ["string"], + "propagated_to": ["string"], "updated_at": "1970-01-01T00:00:00.000Z", "updated_by": "string" } - ] + ], + "defaults": {}, + "readonly_types": ["string"] } ``` @@ -3905,7 +4242,7 @@ Get the configured uniqueness criteria for a specific resource type, if any. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `resource_type` | path | "emailtemplate" \| "product" \| "price" \| "tax" \| "coupon" \| "product_recommendation" \| "file" \| "document_template" \| "schema" \| "taxonomy" \| "notification_template" \| "family" \| "permission" \| "journey" | Yes | | +| `resource_type` | path | "emailtemplate" \| "product" \| "price" \| "tax" \| "coupon" \| "product_recommendation" \| "file" \| "document_template" \| "notification_template" \| "journey" | Yes | | **Sample Call** @@ -3934,6 +4271,7 @@ epilot blueprint-manifest getUniquenessCriteria -p resource_type=example --jsona "org_id": "string", "resource_type": "emailtemplate", "fields": ["string"], + "propagated_to": ["string"], "updated_at": "1970-01-01T00:00:00.000Z", "updated_by": "string" } @@ -3953,7 +4291,7 @@ Set or replace the uniqueness criteria for a resource type. The provided fields | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `resource_type` | path | "emailtemplate" \| "product" \| "price" \| "tax" \| "coupon" \| "product_recommendation" \| "file" \| "document_template" \| "schema" \| "taxonomy" \| "notification_template" \| "family" \| "permission" \| "journey" | Yes | | +| `resource_type` | path | "emailtemplate" \| "product" \| "price" \| "tax" \| "coupon" \| "product_recommendation" \| "file" \| "document_template" \| "notification_template" \| "journey" | Yes | | **Request Body** (required) @@ -3962,7 +4300,7 @@ Set or replace the uniqueness criteria for a resource type. The provided fields ```bash epilot blueprint-manifest putUniquenessCriteria \ -p resource_type=example \ - -d '{"fields":["string"]}' + -d '{"fields":["string"],"propagated_to":["string"]}' ``` Using positional args for path parameters: @@ -3991,6 +4329,7 @@ epilot blueprint-manifest putUniquenessCriteria -p resource_type=example --jsona "org_id": "string", "resource_type": "emailtemplate", "fields": ["string"], + "propagated_to": ["string"], "updated_at": "1970-01-01T00:00:00.000Z", "updated_by": "string" } @@ -4010,7 +4349,7 @@ Remove the custom criteria for a resource type, reverting to the default fields. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `resource_type` | path | "emailtemplate" \| "product" \| "price" \| "tax" \| "coupon" \| "product_recommendation" \| "file" \| "document_template" \| "schema" \| "taxonomy" \| "notification_template" \| "family" \| "permission" \| "journey" | Yes | | +| `resource_type` | path | "emailtemplate" \| "product" \| "price" \| "tax" \| "coupon" \| "product_recommendation" \| "file" \| "document_template" \| "notification_template" \| "journey" | Yes | | **Sample Call** diff --git a/packages/cli/docs/calendar.md b/packages/cli/docs/calendar.md index 78505e91e..9309c88c6 100644 --- a/packages/cli/docs/calendar.md +++ b/packages/cli/docs/calendar.md @@ -12,7 +12,7 @@ epilot's calendar API. epilot calendar # Call an operation -epilot calendar listCalendars +epilot calendar listUsersAbsence -p from=example -p to=example ``` ## Common Flags @@ -35,37 +35,72 @@ epilot calendar listCalendars ## Operations +**Absence** +- [`listUsersAbsence`](#listusersabsence) — List organization users with known absence metadata in the requested time window. +- [`searchAbsence`](#searchabsence) — Search known absence for candidate users in the requested time window. +- [`searchNowAbsence`](#searchnowabsence) — Search known absence for candidate users at the current server time. +- [`listAbsenceAdjustments`](#listabsenceadjustments) — List absence adjustments for a user in a time window. +- [`createAbsenceAdjustment`](#createabsenceadjustment) — Create a time-bound absence adjustment for a user in the caller organization. +- [`getAbsenceAdjustment`](#getabsenceadjustment) — Get an absence adjustment by ID. +- [`patchAbsenceAdjustment`](#patchabsenceadjustment) — Update an absence adjustment in the caller organization. +- [`deleteAbsenceAdjustment`](#deleteabsenceadjustment) — Delete an absence adjustment. +- [`getUserAbsence`](#getuserabsence) — Get known absence for a user in a time window. absent=false means no known absence, not guaranteed availability. + +**Working Hours** +- [`getWorkingHours`](#getworkinghours) — Get the recurring weekly working hours of a user. 404 means no record exists and the user is treated as always available +- [`putWorkingHours`](#putworkinghours) — Create or fully replace the working hours of a user in the caller organization. This is a full replace, not a merge. +- [`deleteWorkingHours`](#deleteworkinghours) — Delete the working hours of a user. The user is then treated as always available again. + **Calendars** - [`listCalendars`](#listcalendars) — List calendars visible to the caller. +- [`createCalendar`](#createcalendar) — Create a native epilot calendar. +- [`addOutlookCalendar`](#addoutlookcalendar) — Registers one of the caller's Outlook calendars as an epilot calendar. +- [`listOutlookCalendars`](#listoutlookcalendars) — Lists the calling user's Outlook calendars available to import as epilot calendars. +- [`deleteOutlookCalendar`](#deleteoutlookcalendar) — Disconnects a previously registered Outlook calendar. +- [`outlookWebhook`](#outlookwebhook) — Public Microsoft Graph webhook receiver for per-user Outlook calendar - [`getCalendar`](#getcalendar) — Get a single calendar by its epilot ID. +- [`updateCalendar`](#updatecalendar) — Update local calendar details. Changes to synced calendars do not modify the provider calendar. +- [`deleteCalendar`](#deletecalendar) — Delete a native epilot calendar or disconnect a synced calendar, including its locally stored events. -**Events** +**Calendar Events** - [`listEvents`](#listevents) — List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own e +- [`createEvent`](#createevent) — Create a native epilot calendar event. Omit `calendar_id` to use the caller’s epilot default calendar. - [`getEvent`](#getevent) — Get a single event by its epilot ID. +- [`updateEvent`](#updateevent) — Update a native epilot calendar event. +- [`deleteEvent`](#deleteevent) — Delete a native epilot calendar event. +- [`shareEvent`](#shareevent) — Share a calendar event with another user of the same organization, view-only. Owner-only: recipients of a share cannot r +- [`unshareEvent`](#unshareevent) — Revoke a per-event share. The recipient loses access immediately. Owner-only. -### `listCalendars` +### `listUsersAbsence` -List calendars visible to the caller. +List organization users with known absence metadata in the requested time window. -`GET /v1/calendar` +`GET /v1/calendar/absence/users` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `size` | query | number | No | | -| `cursor` | query | string | No | Opaque cursor from a previous response | +| `from` | query | string (date-time) | Yes | Start of the time window (inclusive). Maximum window: 31 days. | +| `to` | query | string (date-time) | Yes | End of the time window (exclusive). Must be after from. | +| `include_busy` | query | boolean | No | Include busy inputs in addition to out-of-office absence. Defaults to false. | +| `working_hours_granularity` | query | "time" \| "day" | No | At what granularity working hours contribute to absence. "time" (default): all time outside a user's working windows counts as absent. "day": only full days with no working windows count as absent (wh | +| `query` | query | string | No | Optional user directory search query. | +| `limit` | query | number | No | Maximum users to return. | +| `offset` | query | number | No | User directory offset. | **Sample Call** ```bash -epilot calendar listCalendars +epilot calendar listUsersAbsence \ + -p from=example \ + -p to=example ``` With JSONata filter: ```bash -epilot calendar listCalendars --jsonata 'results[0]' +epilot calendar listUsersAbsence -p from=example -p to=example --jsonata 'results[0]' ```
@@ -73,29 +108,31 @@ epilot calendar listCalendars --jsonata 'results[0]' ```json { + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "hits": 0, "results": [ { - "_id": "string", - "_schema": "calendar", - "_org": "string", - "_title": "string", - "name": "string", - "description": "string", - "color": "string", - "is_default": true, - "read_only": true, - "owner_email": "user@example.com", - "source": { - "type": "native", - "provider": "outlook", - "provider_calendar_id": "string", - "last_synced_at": "1970-01-01T00:00:00.000Z" - }, - "_created_at": "1970-01-01T00:00:00.000Z", - "_updated_at": "1970-01-01T00:00:00.000Z" + "absent": true, + "absence_intervals": [ + { + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "original_from": "1970-01-01T00:00:00.000Z", + "original_to": "1970-01-01T00:00:00.000Z", + "absent": true, + "source": "calendar_event", + "calendar_event_id": "string", + "absence_adjustment_id": "string", + "reason": "string" + } + ], + "user_id": "string", + "display_name": "string", + "email": "user@example.com", + "status": "string" } - ], - "next_cursor": "string" + ] } ``` @@ -103,35 +140,85 @@ epilot calendar listCalendars --jsonata 'results[0]' --- -### `getCalendar` +### `searchAbsence` -Get a single calendar by its epilot ID. +Search known absence for candidate users in the requested time window. -`GET /v1/calendar/{calendar_id}` +`POST /v1/calendar/absence:search` -**Parameters** +**Request Body** (required) -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `calendar_id` | path | string | Yes | | +**Sample Call** + +```bash +epilot calendar searchAbsence +``` + +With request body: + +```bash +epilot calendar searchAbsence \ + -d '{ + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "user_ids": ["string"], + "include_busy": false, + "working_hours_granularity": "time" +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar searchAbsence +``` + +With JSONata filter: + +```bash +epilot calendar searchAbsence --jsonata 'from' +``` + +
+Sample Response + +```json +{ + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "users": {} +} +``` + +
+ +--- + +### `searchNowAbsence` + +Search known absence for candidate users at the current server time. + +`POST /v1/calendar/absence:search-now` + +**Request Body** (required) **Sample Call** ```bash -epilot calendar getCalendar \ - -p calendar_id=123e4567-e89b-12d3-a456-426614174000 +epilot calendar searchNowAbsence \ + -d '{"user_ids":["string"],"include_busy":false,"working_hours_granularity":"time"}' ``` -Using positional args for path parameters: +Using stdin pipe: ```bash -epilot calendar getCalendar 123e4567-e89b-12d3-a456-426614174000 +cat body.json | epilot calendar searchNowAbsence ``` With JSONata filter: ```bash -epilot calendar getCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '_id' +epilot calendar searchNowAbsence --jsonata 'from' ```
@@ -139,24 +226,9 @@ epilot calendar getCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 ```json { - "_id": "string", - "_schema": "calendar", - "_org": "string", - "_title": "string", - "name": "string", - "description": "string", - "color": "string", - "is_default": true, - "read_only": true, - "owner_email": "user@example.com", - "source": { - "type": "native", - "provider": "outlook", - "provider_calendar_id": "string", - "last_synced_at": "1970-01-01T00:00:00.000Z" - }, - "_created_at": "1970-01-01T00:00:00.000Z", - "_updated_at": "1970-01-01T00:00:00.000Z" + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "users": {} } ``` @@ -164,34 +236,39 @@ epilot calendar getCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 --- -### `listEvents` +### `listAbsenceAdjustments` -List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own e +List absence adjustments for a user in a time window. -`GET /v1/calendar/events` +`GET /v1/calendar/absence/users/{user_id}/adjustments` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `from` | query | string (date-time) | Yes | Start of the time window (inclusive) | -| `to` | query | string (date-time) | Yes | End of the time window (exclusive) | -| `calendar_id` | query | string | No | Filter to a single calendar. Omit to query across all accessible calendars. | -| `size` | query | number | No | | -| `cursor` | query | string | No | | +| `user_id` | path | string | Yes | | +| `from` | query | string (date-time) | Yes | Start of the time window (inclusive). Maximum window: 90 days. | +| `to` | query | string (date-time) | Yes | End of the time window (exclusive). Must be after from. | **Sample Call** ```bash -epilot calendar listEvents \ +epilot calendar listAbsenceAdjustments \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ -p from=example \ -p to=example ``` +Using positional args for path parameters: + +```bash +epilot calendar listAbsenceAdjustments 123e4567-e89b-12d3-a456-426614174000 +``` + With JSONata filter: ```bash -epilot calendar listEvents -p from=example -p to=example --jsonata 'results[0]' +epilot calendar listAbsenceAdjustments -p user_id=123e4567-e89b-12d3-a456-426614174000 -p from=example -p to=example --jsonata 'results[0]' ```
@@ -199,54 +276,23 @@ epilot calendar listEvents -p from=example -p to=example --jsonata 'results[0]' ```json { + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", "results": [ { - "_id": "string", - "_schema": "calendar_event", - "_org": "string", - "_title": "string", - "_tags": ["string"], - "calendar_id": "string", - "event_type": "singleInstance", - "description": "string", - "start_time": "1970-01-01T00:00:00.000Z", - "end_time": "1970-01-01T00:00:00.000Z", - "timezone": "string", - "is_all_day": true, - "location": "string", - "status": "free", - "busy": true, - "is_cancelled": true, - "sensitivity": "normal", - "importance": "low", - "is_online_meeting": true, - "online_meeting_url": "https://example.com/path", - "web_link": "https://example.com/path", - "response_status": "none", - "organizer_email": "user@example.com", - "attendees": [ - { - "email": "user@example.com", - "name": "string", - "response": "none", - "type": "required" - } - ], - "is_recurring": true, - "series_master_id": "string", - "source": { - "type": "native", - "provider": "outlook", - "provider_event_id": "string", - "provider_event_url": "https://example.com/path", - "last_synced_at": "1970-01-01T00:00:00.000Z", - "etag": "string" - }, - "_created_at": "1970-01-01T00:00:00.000Z", - "_updated_at": "1970-01-01T00:00:00.000Z" + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "absent": true, + "status": "oof", + "type": "string", + "reason": "string", + "adjustment_id": "string", + "user_id": "string", + "created_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" } - ], - "next_cursor": "string" + ] } ``` @@ -254,35 +300,58 @@ epilot calendar listEvents -p from=example -p to=example --jsonata 'results[0]' --- -### `getEvent` +### `createAbsenceAdjustment` -Get a single event by its epilot ID. +Create a time-bound absence adjustment for a user in the caller organization. -`GET /v1/calendar/events/{event_id}` +`POST /v1/calendar/absence/users/{user_id}/adjustments` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `event_id` | path | string | Yes | | +| `user_id` | path | string | Yes | | + +**Request Body** (required) **Sample Call** ```bash -epilot calendar getEvent \ - -p event_id=123e4567-e89b-12d3-a456-426614174000 +epilot calendar createAbsenceAdjustment \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With request body: + +```bash +epilot calendar createAbsenceAdjustment \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{ + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "absent": true, + "status": "oof", + "type": "string", + "reason": "string" +}' ``` Using positional args for path parameters: ```bash -epilot calendar getEvent 123e4567-e89b-12d3-a456-426614174000 +epilot calendar createAbsenceAdjustment 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar createAbsenceAdjustment -p user_id=123e4567-e89b-12d3-a456-426614174000 ``` With JSONata filter: ```bash -epilot calendar getEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '_id' +epilot calendar createAbsenceAdjustment -p user_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'from' ```
@@ -290,52 +359,1485 @@ epilot calendar getEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 --json ```json { - "_id": "string", - "_schema": "calendar_event", - "_org": "string", - "_title": "string", - "_tags": ["string"], - "calendar_id": "string", - "event_type": "singleInstance", - "description": "string", - "start_time": "1970-01-01T00:00:00.000Z", - "end_time": "1970-01-01T00:00:00.000Z", - "timezone": "string", - "is_all_day": true, - "location": "string", - "status": "free", - "busy": true, - "is_cancelled": true, - "sensitivity": "normal", - "importance": "low", - "is_online_meeting": true, - "online_meeting_url": "https://example.com/path", - "web_link": "https://example.com/path", - "response_status": "none", - "organizer_email": "user@example.com", - "attendees": [ - { - "email": "user@example.com", - "name": "string", - "response": "none", - "type": "required" - } - ], - "is_recurring": true, - "series_master_id": "string", - "source": { - "type": "native", - "provider": "outlook", - "provider_event_id": "string", - "provider_event_url": "https://example.com/path", - "last_synced_at": "1970-01-01T00:00:00.000Z", - "etag": "string" - }, - "_created_at": "1970-01-01T00:00:00.000Z", - "_updated_at": "1970-01-01T00:00:00.000Z" + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "absent": true, + "status": "oof", + "type": "string", + "reason": "string", + "adjustment_id": "string", + "user_id": "string", + "created_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `getAbsenceAdjustment` + +Get an absence adjustment by ID. + +`GET /v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `user_id` | path | string | Yes | | +| `adjustment_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar getAbsenceAdjustment \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ + -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar getAbsenceAdjustment 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar getAbsenceAdjustment -p user_id=123e4567-e89b-12d3-a456-426614174000 -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'from' +``` + +
+Sample Response + +```json +{ + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "absent": true, + "status": "oof", + "type": "string", + "reason": "string", + "adjustment_id": "string", + "user_id": "string", + "created_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" } ```
--- + +### `patchAbsenceAdjustment` + +Update an absence adjustment in the caller organization. + +`PATCH /v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `user_id` | path | string | Yes | | +| `adjustment_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar patchAbsenceAdjustment \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ + -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With request body: + +```bash +epilot calendar patchAbsenceAdjustment \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ + -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{ + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "absent": true, + "status": "oof", + "type": "string", + "reason": "string" +}' +``` + +Using positional args for path parameters: + +```bash +epilot calendar patchAbsenceAdjustment 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar patchAbsenceAdjustment -p user_id=123e4567-e89b-12d3-a456-426614174000 -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar patchAbsenceAdjustment -p user_id=123e4567-e89b-12d3-a456-426614174000 -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'from' +``` + +
+Sample Response + +```json +{ + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "absent": true, + "status": "oof", + "type": "string", + "reason": "string", + "adjustment_id": "string", + "user_id": "string", + "created_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteAbsenceAdjustment` + +Delete an absence adjustment. + +`DELETE /v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `user_id` | path | string | Yes | | +| `adjustment_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar deleteAbsenceAdjustment \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ + -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar deleteAbsenceAdjustment 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar deleteAbsenceAdjustment -p user_id=123e4567-e89b-12d3-a456-426614174000 -p adjustment_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `getUserAbsence` + +Get known absence for a user in a time window. absent=false means no known absence, not guaranteed availability. + +`GET /v1/calendar/absence/users/{user_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `user_id` | path | string | Yes | | +| `from` | query | string (date-time) | Yes | Start of the time window (inclusive). Maximum window: 31 days. | +| `to` | query | string (date-time) | Yes | End of the time window (exclusive). Must be after from. | +| `include_busy` | query | boolean | No | Include busy inputs in addition to out-of-office absence. Defaults to false. | +| `working_hours_granularity` | query | "time" \| "day" | No | At what granularity working hours contribute to absence. "time" (default): all time outside a user's working windows counts as absent. "day": only full days with no working windows count as absent (wh | + +**Sample Call** + +```bash +epilot calendar getUserAbsence \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ + -p from=example \ + -p to=example +``` + +Using positional args for path parameters: + +```bash +epilot calendar getUserAbsence 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar getUserAbsence -p user_id=123e4567-e89b-12d3-a456-426614174000 -p from=example -p to=example --jsonata 'absent' +``` + +
+Sample Response + +```json +{ + "absent": true, + "absence_intervals": [ + { + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "original_from": "1970-01-01T00:00:00.000Z", + "original_to": "1970-01-01T00:00:00.000Z", + "absent": true, + "source": "calendar_event", + "calendar_event_id": "string", + "absence_adjustment_id": "string", + "reason": "string" + } + ], + "from": "1970-01-01T00:00:00.000Z", + "to": "1970-01-01T00:00:00.000Z", + "user_id": "string", + "external_calendars": [ + { + "provider": "outlook", + "last_synced_at": "1970-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `getWorkingHours` + +Get the recurring weekly working hours of a user. 404 means no record exists and the user is treated as always available + +`GET /v1/calendar/working-hours/users/{user_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `user_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar getWorkingHours \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar getWorkingHours 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar getWorkingHours -p user_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'monday' +``` + +
+Sample Response + +```json +{ + "monday": [ + { + "start": "string", + "end": "string" + } + ], + "tuesday": [ + { + "start": "string", + "end": "string" + } + ], + "wednesday": [ + { + "start": "string", + "end": "string" + } + ], + "thursday": [ + { + "start": "string", + "end": "string" + } + ], + "friday": [ + { + "start": "string", + "end": "string" + } + ], + "saturday": [ + { + "start": "string", + "end": "string" + } + ], + "sunday": [ + { + "start": "string", + "end": "string" + } + ], + "timezone": "string", + "user_id": "string", + "updated_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `putWorkingHours` + +Create or fully replace the working hours of a user in the caller organization. This is a full replace, not a merge. + +`PUT /v1/calendar/working-hours/users/{user_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `user_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar putWorkingHours \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With request body: + +```bash +epilot calendar putWorkingHours \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{ + "monday": [ + { + "start": "string", + "end": "string" + } + ], + "tuesday": [ + { + "start": "string", + "end": "string" + } + ], + "wednesday": [ + { + "start": "string", + "end": "string" + } + ], + "thursday": [ + { + "start": "string", + "end": "string" + } + ], + "friday": [ + { + "start": "string", + "end": "string" + } + ], + "saturday": [ + { + "start": "string", + "end": "string" + } + ], + "sunday": [ + { + "start": "string", + "end": "string" + } + ], + "timezone": "Europe/Berlin" +}' +``` + +Using positional args for path parameters: + +```bash +epilot calendar putWorkingHours 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar putWorkingHours -p user_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar putWorkingHours -p user_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'monday' +``` + +
+Sample Response + +```json +{ + "monday": [ + { + "start": "string", + "end": "string" + } + ], + "tuesday": [ + { + "start": "string", + "end": "string" + } + ], + "wednesday": [ + { + "start": "string", + "end": "string" + } + ], + "thursday": [ + { + "start": "string", + "end": "string" + } + ], + "friday": [ + { + "start": "string", + "end": "string" + } + ], + "saturday": [ + { + "start": "string", + "end": "string" + } + ], + "sunday": [ + { + "start": "string", + "end": "string" + } + ], + "timezone": "string", + "user_id": "string", + "updated_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteWorkingHours` + +Delete the working hours of a user. The user is then treated as always available again. + +`DELETE /v1/calendar/working-hours/users/{user_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `user_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar deleteWorkingHours \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar deleteWorkingHours 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar deleteWorkingHours -p user_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `listCalendars` + +List calendars visible to the caller. + +`GET /v1/calendar` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `size` | query | number | No | | +| `cursor` | query | string | No | Opaque cursor from a previous response | + +**Sample Call** + +```bash +epilot calendar listCalendars +``` + +With JSONata filter: + +```bash +epilot calendar listCalendars --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "_id": "string", + "_schema": "calendar", + "_org": "string", + "_title": "string", + "name": "string", + "description": "string", + "color": "string", + "is_default": true, + "is_epilot_default": true, + "read_only": true, + "owner_email": "user@example.com", + "source": { + "type": "native", + "provider": "outlook", + "provider_calendar_id": "string", + "last_synced_at": "1970-01-01T00:00:00.000Z" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `createCalendar` + +Create a native epilot calendar. + +`POST /v1/calendar` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar createCalendar \ + -d '{"name":"string","description":"string","color":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar createCalendar +``` + +With JSONata filter: + +```bash +epilot calendar createCalendar --jsonata '_id' +``` + +
+Sample Response + +```json +{ + "_id": "string", + "_schema": "calendar", + "_org": "string", + "_title": "string", + "name": "string", + "description": "string", + "color": "string", + "is_default": true, + "is_epilot_default": true, + "read_only": true, + "owner_email": "user@example.com", + "source": { + "type": "native", + "provider": "outlook", + "provider_calendar_id": "string", + "last_synced_at": "1970-01-01T00:00:00.000Z" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `addOutlookCalendar` + +Registers one of the caller's Outlook calendars as an epilot calendar. + +`POST /v1/calendar/sources/outlook` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar addOutlookCalendar \ + -d '{"provider_calendar_id":"string","name":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar addOutlookCalendar +``` + +With JSONata filter: + +```bash +epilot calendar addOutlookCalendar --jsonata '_id' +``` + +
+Sample Response + +```json +{ + "_id": "string", + "_schema": "calendar", + "_org": "string", + "_title": "string", + "name": "string", + "description": "string", + "color": "string", + "is_default": true, + "is_epilot_default": true, + "read_only": true, + "owner_email": "user@example.com", + "source": { + "type": "native", + "provider": "outlook", + "provider_calendar_id": "string", + "last_synced_at": "1970-01-01T00:00:00.000Z" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `listOutlookCalendars` + +Lists the calling user's Outlook calendars available to import as epilot calendars. + +`GET /v1/calendar/sources/outlook/available` + +**Sample Call** + +```bash +epilot calendar listOutlookCalendars +``` + +With JSONata filter: + +```bash +epilot calendar listOutlookCalendars --jsonata 'calendars' +``` + +
+Sample Response + +```json +{ + "calendars": [ + { + "provider_calendar_id": "string", + "name": "string", + "color": "string", + "is_default": true, + "can_edit": true, + "owner": "string" + } + ] +} +``` + +
+ +--- + +### `deleteOutlookCalendar` + +Disconnects a previously registered Outlook calendar. + +`DELETE /v1/calendar/sources/outlook/{calendar_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `calendar_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar deleteOutlookCalendar \ + -p calendar_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar deleteOutlookCalendar 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar deleteOutlookCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `outlookWebhook` + +Public Microsoft Graph webhook receiver for per-user Outlook calendar + +`POST /v1/calendar/outlook/webhook` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `validationToken` | query | string | No | Set by Graph on the subscription-validation handshake; echoed back verbatim. | + +**Sample Call** + +```bash +epilot calendar outlookWebhook +``` + +With JSONata filter: + +```bash +epilot calendar outlookWebhook --jsonata '$' +``` + +--- + +### `getCalendar` + +Get a single calendar by its epilot ID. + +`GET /v1/calendar/{calendar_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `calendar_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar getCalendar \ + -p calendar_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar getCalendar 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar getCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '_id' +``` + +
+Sample Response + +```json +{ + "_id": "string", + "_schema": "calendar", + "_org": "string", + "_title": "string", + "name": "string", + "description": "string", + "color": "string", + "is_default": true, + "is_epilot_default": true, + "read_only": true, + "owner_email": "user@example.com", + "source": { + "type": "native", + "provider": "outlook", + "provider_calendar_id": "string", + "last_synced_at": "1970-01-01T00:00:00.000Z" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `updateCalendar` + +Update local calendar details. Changes to synced calendars do not modify the provider calendar. + +`PATCH /v1/calendar/{calendar_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `calendar_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar updateCalendar \ + -p calendar_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"name":"string","description":"string","color":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot calendar updateCalendar 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar updateCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar updateCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '_id' +``` + +
+Sample Response + +```json +{ + "_id": "string", + "_schema": "calendar", + "_org": "string", + "_title": "string", + "name": "string", + "description": "string", + "color": "string", + "is_default": true, + "is_epilot_default": true, + "read_only": true, + "owner_email": "user@example.com", + "source": { + "type": "native", + "provider": "outlook", + "provider_calendar_id": "string", + "last_synced_at": "1970-01-01T00:00:00.000Z" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteCalendar` + +Delete a native epilot calendar or disconnect a synced calendar, including its locally stored events. + +`DELETE /v1/calendar/{calendar_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `calendar_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar deleteCalendar \ + -p calendar_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar deleteCalendar 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar deleteCalendar -p calendar_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `listEvents` + +List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own e + +`GET /v1/calendar/events` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `from` | query | string (date-time) | Yes | Start of the time window (inclusive) | +| `to` | query | string (date-time) | Yes | End of the time window (exclusive) | +| `calendar_id` | query | string | No | Filter to a single calendar. Omit to query across all accessible calendars. | +| `size` | query | number | No | | +| `cursor` | query | string | No | | + +**Sample Call** + +```bash +epilot calendar listEvents \ + -p from=example \ + -p to=example +``` + +With JSONata filter: + +```bash +epilot calendar listEvents -p from=example -p to=example --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "_id": "string", + "_schema": "calendar_event", + "_org": "string", + "_title": "string", + "_tags": ["string"], + "calendar_id": "string", + "event_type": "singleInstance", + "description": "string", + "start_time": "1970-01-01T00:00:00.000Z", + "end_time": "1970-01-01T00:00:00.000Z", + "timezone": "string", + "is_all_day": true, + "location": "string", + "status": "free", + "busy": true, + "is_cancelled": true, + "is_draft": true, + "sensitivity": "normal", + "importance": "low", + "is_online_meeting": true, + "online_meeting_url": "https://example.com/path", + "web_link": "https://example.com/path", + "response_status": "none", + "organizer_email": "user@example.com", + "attendees": [ + { + "email": "user@example.com", + "name": "string", + "response": "none", + "type": "required" + } + ], + "metadata": {}, + "is_recurring": true, + "series_master_id": "string", + "source": { + "type": "native", + "provider": "outlook", + "provider_event_id": "string", + "provider_event_url": "https://example.com/path", + "last_synced_at": "1970-01-01T00:00:00.000Z", + "etag": "string" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `createEvent` + +Create a native epilot calendar event. Omit `calendar_id` to use the caller’s epilot default calendar. + +`POST /v1/calendar/events` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar createEvent +``` + +With request body: + +```bash +epilot calendar createEvent \ + -d '{ + "calendar_id": "string", + "description": "string", + "start_time": "1970-01-01T00:00:00.000Z", + "end_time": "1970-01-01T00:00:00.000Z", + "timezone": "string", + "is_all_day": true, + "location": "string", + "status": "free", + "sensitivity": "normal", + "metadata": {}, + "_title": "string" +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar createEvent +``` + +With JSONata filter: + +```bash +epilot calendar createEvent --jsonata '_id' +``` + +
+Sample Response + +```json +{ + "_id": "string", + "_schema": "calendar_event", + "_org": "string", + "_title": "string", + "_tags": ["string"], + "calendar_id": "string", + "event_type": "singleInstance", + "description": "string", + "start_time": "1970-01-01T00:00:00.000Z", + "end_time": "1970-01-01T00:00:00.000Z", + "timezone": "string", + "is_all_day": true, + "location": "string", + "status": "free", + "busy": true, + "is_cancelled": true, + "is_draft": true, + "sensitivity": "normal", + "importance": "low", + "is_online_meeting": true, + "online_meeting_url": "https://example.com/path", + "web_link": "https://example.com/path", + "response_status": "none", + "organizer_email": "user@example.com", + "attendees": [ + { + "email": "user@example.com", + "name": "string", + "response": "none", + "type": "required" + } + ], + "metadata": {}, + "is_recurring": true, + "series_master_id": "string", + "source": { + "type": "native", + "provider": "outlook", + "provider_event_id": "string", + "provider_event_url": "https://example.com/path", + "last_synced_at": "1970-01-01T00:00:00.000Z", + "etag": "string" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `getEvent` + +Get a single event by its epilot ID. + +`GET /v1/calendar/events/{event_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar getEvent \ + -p event_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar getEvent 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar getEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '_id' +``` + +
+Sample Response + +```json +{ + "_id": "string", + "_schema": "calendar_event", + "_org": "string", + "_title": "string", + "_tags": ["string"], + "calendar_id": "string", + "event_type": "singleInstance", + "description": "string", + "start_time": "1970-01-01T00:00:00.000Z", + "end_time": "1970-01-01T00:00:00.000Z", + "timezone": "string", + "is_all_day": true, + "location": "string", + "status": "free", + "busy": true, + "is_cancelled": true, + "is_draft": true, + "sensitivity": "normal", + "importance": "low", + "is_online_meeting": true, + "online_meeting_url": "https://example.com/path", + "web_link": "https://example.com/path", + "response_status": "none", + "organizer_email": "user@example.com", + "attendees": [ + { + "email": "user@example.com", + "name": "string", + "response": "none", + "type": "required" + } + ], + "metadata": {}, + "is_recurring": true, + "series_master_id": "string", + "source": { + "type": "native", + "provider": "outlook", + "provider_event_id": "string", + "provider_event_url": "https://example.com/path", + "last_synced_at": "1970-01-01T00:00:00.000Z", + "etag": "string" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `updateEvent` + +Update a native epilot calendar event. + +`PATCH /v1/calendar/events/{event_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar updateEvent \ + -p event_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With request body: + +```bash +epilot calendar updateEvent \ + -p event_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{ + "description": "string", + "start_time": "1970-01-01T00:00:00.000Z", + "end_time": "1970-01-01T00:00:00.000Z", + "timezone": "string", + "is_all_day": true, + "location": "string", + "status": "free", + "is_cancelled": true, + "sensitivity": "normal", + "_title": "string" +}' +``` + +Using positional args for path parameters: + +```bash +epilot calendar updateEvent 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar updateEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar updateEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '_id' +``` + +
+Sample Response + +```json +{ + "_id": "string", + "_schema": "calendar_event", + "_org": "string", + "_title": "string", + "_tags": ["string"], + "calendar_id": "string", + "event_type": "singleInstance", + "description": "string", + "start_time": "1970-01-01T00:00:00.000Z", + "end_time": "1970-01-01T00:00:00.000Z", + "timezone": "string", + "is_all_day": true, + "location": "string", + "status": "free", + "busy": true, + "is_cancelled": true, + "is_draft": true, + "sensitivity": "normal", + "importance": "low", + "is_online_meeting": true, + "online_meeting_url": "https://example.com/path", + "web_link": "https://example.com/path", + "response_status": "none", + "organizer_email": "user@example.com", + "attendees": [ + { + "email": "user@example.com", + "name": "string", + "response": "none", + "type": "required" + } + ], + "metadata": {}, + "is_recurring": true, + "series_master_id": "string", + "source": { + "type": "native", + "provider": "outlook", + "provider_event_id": "string", + "provider_event_url": "https://example.com/path", + "last_synced_at": "1970-01-01T00:00:00.000Z", + "etag": "string" + }, + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteEvent` + +Delete a native epilot calendar event. + +`DELETE /v1/calendar/events/{event_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar deleteEvent \ + -p event_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar deleteEvent 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar deleteEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `shareEvent` + +Share a calendar event with another user of the same organization, view-only. Owner-only: recipients of a share cannot r + +`POST /v1/calendar/events/{event_id}/share` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot calendar shareEvent \ + -p event_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"user_id":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot calendar shareEvent 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot calendar shareEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar shareEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `unshareEvent` + +Revoke a per-event share. The recipient loses access immediately. Owner-only. + +`DELETE /v1/calendar/events/{event_id}/share/{user_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_id` | path | string | Yes | | +| `user_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot calendar unshareEvent \ + -p event_id=123e4567-e89b-12d3-a456-426614174000 \ + -p user_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot calendar unshareEvent 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot calendar unshareEvent -p event_id=123e4567-e89b-12d3-a456-426614174000 -p user_id=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- diff --git a/packages/cli/docs/customer-portal.md b/packages/cli/docs/customer-portal.md index 343fef171..197534dd2 100644 --- a/packages/cli/docs/customer-portal.md +++ b/packages/cli/docs/customer-portal.md @@ -48,6 +48,8 @@ epilot customer-portal upsertPortal -p origin=example - [`getAllPortalConfigs`](#getallportalconfigs) — Retrieves all portal configurations. - [`getEmailTemplates`](#getemailtemplates) — Retrieves the email templates of a portal - [`upsertEmailTemplates`](#upsertemailtemplates) — Upserts the email templates of a portal +- [`migrateEmailTemplateReferences`](#migrateemailtemplatereferences) — Walk every email-template config row in the caller's org and re-point any +- [`listEmailTemplateReferences`](#listemailtemplatereferences) — Read-only sibling of migrateEmailTemplateReferences. Lists every portal in - [`getEmailTemplatesByPortalId`](#getemailtemplatesbyportalid) — Retrieves the email templates of a portal by portal ID - [`upsertEmailTemplatesByPortalId`](#upsertemailtemplatesbyportalid) — Upserts the email templates of a portal by portal ID - [`getPortalWidgetsV3`](#getportalwidgetsv3) — Retrieves the widgets of a portal by portal_id. @@ -85,6 +87,8 @@ epilot customer-portal upsertPortal -p origin=example - [`swapPortalConfig`](#swapportalconfig) — Swaps the portal configuration of two portals. - [`clonePortalConfig`](#cloneportalconfig) — Creates a new portal by cloning configuration and pages from an existing portal. The new portal gets its own domain, use - [`verifyDns`](#verifydns) — Manually triggers DNS verification for a portal's domain setup. Runs the same verification logic as the scheduled proces +- [`getMobileConfig`](#getmobileconfig) — Returns the portal's mobile app configuration. By default the response is build-ready (resolved): base info (display_nam +- [`putMobileConfig`](#putmobileconfig) — Merges the provided fields into the portal's mobile app configuration **Public** - [`createUserV3`](#createuserv3) — Registers a portal user. @@ -115,6 +119,8 @@ epilot customer-portal upsertPortal -p origin=example **ECP** - [`validateToken`](#validatetoken) — Validates Portal Token is valid. Pass the token via Authorization Header. - [`revokeToken`](#revoketoken) — Revokes all of the access tokens for the given Refresh Token. +- [`createExport`](#createexport) — Request an asynchronous CSV export of the portal user's entities for one schema. Columns come from the request Returns a +- [`getExport`](#getexport) — Get the status of an export job, including the download URL once ready. - [`getConsumption`](#getconsumption) — Get energy consumption data between a given time period. - [`prepareVisualizationExport`](#preparevisualizationexport) — Asks an installed App to prepare a downloadable export of a visualization (consumption chart, dynamic tariff chart, etc. - [`getVisualizationMetadata`](#getvisualizationmetadata) — Returns runtime metadata describing how a visualization (consumption / price / cost chart) should be rendered for a give @@ -127,13 +133,16 @@ epilot customer-portal upsertPortal -p origin=example - [`updatePortalUser`](#updateportaluser) — Update the portal user details - [`deletePortalUser`](#deleteportaluser) — Delete the portal user - [`updatePortalUserEmail`](#updateportaluseremail) — Update portal user email +- [`changePortalUserPassword`](#changeportaluserpassword) — Hand over a password change to the third-party system configured via the `changePassword` portal extension hook. - [`postOrderAcceptance`](#postorderacceptance) — Accept/decline an offer by id +- [`getContractWithTemplates`](#getcontractwithtemplates) — Resolve Handlebars templates against a contract's related meters and return the contract with templates_output populated - [`addContractByIdentifiers`](#addcontractbyidentifiers) — Self-assign contract(s) by pre-configured identifiers. - [`validateCadenceEntityEditRules`](#validatecadenceentityeditrules) — Validate if cadence rule is valid for an entity - [`searchPaymentRelationsInEntities`](#searchpaymentrelationsinentities) — Search for entities that have the payment relation with the given payment id - [`createCustomEntityActivity`](#createcustomentityactivity) — Create a custom activity that can be displayed in activity feed of an entity. - [`saveEntityFile`](#saveentityfile) — Add files to an entity - [`deleteEntityFile`](#deleteentityfile) — Delete files from an entity +- [`getFilePreview`](#getfilepreview) — resolves an in-portal preview for a file. Returns a Content-Disposition: inline URL for directly-previewable files (PDF, - [`trackFileDownloaded`](#trackfiledownloaded) — Track that user has downloaded a file - [`getBillingEvents`](#getbillingevents) — Fetch billing events for a portal user - [`triggerEntityAccessEvent`](#triggerentityaccessevent) — Trigger entity access event for a portal user @@ -143,6 +152,9 @@ epilot customer-portal upsertPortal -p origin=example - [`getAutomationContext`](#getautomationcontext) — Retrieves the automation context. - [`updateWorkflowStepAsDone`](#updateworkflowstepasdone) — Update a workflow step as done - [`getEntityWorkflows`](#getentityworkflows) — Get all workflows associated with an entity (requires access to the entity) +- [`getOutstandingTasks`](#getoutstandingtasks) — Returns all outstanding workflow journey tasks for the authenticated portal user, across their opportunity, order and co +- [`getEntityPortalWorkflows`](#getentityportalworkflows) — Get all portal-relevant workflows associated with an entity (requires access to the entity), +- [`getEntityPortalWorkflowsBatch`](#getentityportalworkflowsbatch) — Batch variant of `getEntityPortalWorkflows`: returns portal-relevant workflows for - [`uploadMeterReadingPhoto`](#uploadmeterreadingphoto) — Uploads a Meter Reading photo and - if enabled - gives back data extracted from the photo. - [`createMeterReading`](#createmeterreading) — Inserts a new meter reading. - [`getAllowedMeterReadingRange`](#getallowedmeterreadingrange) — Get allowed reading range for all counters of a meter from the configured @@ -153,6 +165,10 @@ epilot customer-portal upsertPortal -p origin=example - [`getPortalPageBlocks`](#getportalpageblocks) — Fetch all portal page blocks - [`getPortalPageBlock`](#getportalpageblock) — Fetch a portal page block by id - [`updateCampaignPortalBlockStatus`](#updatecampaignportalblockstatus) — Updates the status of a campaign portal block for multiple recipients. +- [`listPortalNotifications`](#listportalnotifications) — Lists the 360 notifications addressed to the authenticated portal user, newest first. The organization and the portal us +- [`getPortalNotificationsUnreadCount`](#getportalnotificationsunreadcount) — Returns the number of unread notifications for the authenticated portal user. +- [`markAllPortalNotificationsRead`](#markallportalnotificationsread) — Marks all notifications of the authenticated portal user as read. +- [`markPortalNotificationRead`](#markportalnotificationread) — Marks a single notification of the authenticated portal user as read. - [`updateNotificationsStatus`](#updatenotificationsstatus) — Updates the statuses of multiple notifications at once. - [`invitePartner`](#invitepartner) — Invites a partner to a portal - [`listBusinessPartners`](#listbusinesspartners) — Lists all business partners linked to the businessaccount @@ -231,9 +247,47 @@ epilot customer-portal upsertPortal \ "attribute_mappings": {}, "entity_matching": {}, "oidc_config": {}, - "mobile_oidc_config": {} + "mobile_oidc_config": {}, + "expose_client_secret": false + } + ], + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" } - ], + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -267,7 +321,8 @@ epilot customer-portal upsertPortal \ }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -289,7 +344,8 @@ epilot customer-portal upsertPortal \ "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -412,7 +468,16 @@ epilot customer-portal upsertPortal \ "portal_sk_v3": "PORTAL_CONFIG#453ad7bf-86d5-46c8-8252-bcc868df5e3c", "origin": "string", "pages": {}, - "global_blocks": {} + "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true }' ``` @@ -433,6 +498,43 @@ epilot customer-portal upsertPortal -p origin=example --jsonata '$' ```json { + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -466,7 +568,8 @@ epilot customer-portal upsertPortal -p origin=example --jsonata '$' }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -488,7 +591,8 @@ epilot customer-portal upsertPortal -p origin=example --jsonata '$' "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -612,6 +716,15 @@ epilot customer-portal upsertPortal -p origin=example --jsonata '$' "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": { @@ -821,6 +934,43 @@ epilot customer-portal getPortalConfigByDomain -p domain=example.com --jsonata ' ```json { + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -854,7 +1004,8 @@ epilot customer-portal getPortalConfigByDomain -p domain=example.com --jsonata ' }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -876,7 +1027,8 @@ epilot customer-portal getPortalConfigByDomain -p domain=example.com --jsonata ' "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -1000,6 +1152,15 @@ epilot customer-portal getPortalConfigByDomain -p domain=example.com --jsonata ' "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": { @@ -1062,6 +1223,43 @@ epilot customer-portal getPortalConfig --jsonata '$' ```json { + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -1095,7 +1293,8 @@ epilot customer-portal getPortalConfig --jsonata '$' }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -1117,7 +1316,8 @@ epilot customer-portal getPortalConfig --jsonata '$' "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -1241,6 +1441,15 @@ epilot customer-portal getPortalConfig --jsonata '$' "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": { @@ -1301,6 +1510,119 @@ epilot customer-portal deletePortal -p origin=example --jsonata '$' --- +### `createExport` + +Request an asynchronous CSV export of the portal user's entities for one schema. Columns come from the request Returns a + +`POST /v1/portal/exports` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot customer-portal createExport +``` + +With request body: + +```bash +epilot customer-portal createExport \ + -d '{ + "schema": "string", + "search": { + "q": "string", + "q_fields": ["string"], + "filters": [ + {} + ], + "filters_context": [ + {} + ], + "sort": {} + }, + "columns": [ + { + "key": "vertragsnummer", + "header": { + "de": "Vertragsnummer", + "en": "Contract number" + }, + "source": { + "path": ["customer"], + "attribute": "customer_number", + "address_field": "full" + }, + "formatter": "text", + "enum_labels": {} + } + ], + "expand_over": "string", + "language": "de" +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot customer-portal createExport +``` + +With JSONata filter: + +```bash +epilot customer-portal createExport --jsonata '$' +``` + +--- + +### `getExport` + +Get the status of an export job, including the download URL once ready. + +`GET /v1/portal/exports/{jobId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `jobId` | path | string | Yes | The export job id returned by createExport. | + +**Sample Call** + +```bash +epilot customer-portal getExport \ + -p jobId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot customer-portal getExport 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot customer-portal getExport -p jobId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'jobId' +``` + +
+Sample Response + +```json +{ + "jobId": "string", + "status": "queued", + "downloadUrl": "string", + "error": "string" +} +``` + +
+ +--- + ### `getPortalExtensions` Retrieves the installed portal extensions. @@ -1439,6 +1761,51 @@ epilot customer-portal getPublicPortalExtensionDetails -p org_id=12324 -p origin "hook": { "plausibility_mode": "check" } + }, + "changeEmail": { + "app": { + "app_id": "string", + "name": {} + }, + "extension": { + "id": "string", + "name": {} + }, + "hook": { + "id": "string", + "change_mode": "asynchronous", + "require_password_confirmation": true, + "explanation": {} + } + }, + "changePassword": { + "app": { + "app_id": "string", + "name": {} + }, + "extension": { + "id": "string", + "name": {} + }, + "hook": { + "id": "string", + "require_new_password": false, + "explanation": {} + } + }, + "deleteAccount": { + "app": { + "app_id": "string", + "name": {} + }, + "extension": { + "id": "string", + "name": {} + }, + "hook": { + "id": "string", + "explanation": {} + } } } ``` @@ -1585,6 +1952,51 @@ epilot customer-portal getPublicPortalExtensionDetailsV3 --jsonata 'consumptionD "hook": { "plausibility_mode": "check" } + }, + "changeEmail": { + "app": { + "app_id": "string", + "name": {} + }, + "extension": { + "id": "string", + "name": {} + }, + "hook": { + "id": "string", + "change_mode": "asynchronous", + "require_password_confirmation": true, + "explanation": {} + } + }, + "changePassword": { + "app": { + "app_id": "string", + "name": {} + }, + "extension": { + "id": "string", + "name": {} + }, + "hook": { + "id": "string", + "require_new_password": false, + "explanation": {} + } + }, + "deleteAccount": { + "app": { + "app_id": "string", + "name": {} + }, + "extension": { + "id": "string", + "name": {} + }, + "hook": { + "id": "string", + "explanation": {} + } } } ``` @@ -1609,7 +2021,7 @@ Get energy consumption data between a given time period. | `meter_id` | query | string | No | Meter ID for consumption data. Deprecated - use context_entities instead. | | `from` | query | string (date-time) | Yes | Start date for consumption data (ISO 8601 format). | | `to` | query | string (date-time) | Yes | End date for consumption data (ISO 8601 format). | -| `interval` | query | "PT15M" \| "PT1H" \| "P1D" \| "P1M" | Yes | Interval between consumption data points (e.g., PT15M for 15 minutes, PT1H for hourly). Not all intervals have to be supported. | +| `interval` | query | "PT15M" \| "PT1H" \| "P1D" \| "P1M" \| "P1Y" | Yes | Interval between consumption data points (e.g., PT15M for 15 minutes, PT1H for hourly). Not all intervals have to be supported. | | `context_entities` | query | object[] | No | Additional entities to include in the context for variable interpolation in the hook. | **Sample Call** @@ -1638,7 +2050,11 @@ epilot customer-portal getConsumption -p extensionId=123e4567-e89b-12d3-a456-426 "timestamp": "1970-01-01T00:00:00.000Z", "value": 0, "type": "nt", - "unit": "kWh" + "unit": "kWh", + "label": { + "en": "Billing period 1", + "de": "Abrechnungszeitraum 1" + } } ] } @@ -1781,7 +2197,7 @@ Get energy cost data between a given time period. | `meter_id` | query | string | No | Meter ID for cost data. Deprecated - use context_entities instead. | | `from` | query | string (date-time) | Yes | Start date for cost data (ISO 8601 format). | | `to` | query | string (date-time) | Yes | End date for cost data (ISO 8601 format). | -| `interval` | query | "PT15M" \| "PT1H" \| "P1D" \| "P1M" | Yes | Interval between cost data points (e.g., PT15M for 15 minutes, PT1H for hourly). Not all intervals have to be supported. | +| `interval` | query | "PT15M" \| "PT1H" \| "P1D" \| "P1M" \| "P1Y" | Yes | Interval between cost data points (e.g., PT15M for 15 minutes, PT1H for hourly). Not all intervals have to be supported. | | `context_entities` | query | object[] | No | Additional entities to include in the context for variable interpolation in the hook. | **Sample Call** @@ -1838,7 +2254,7 @@ Get energy prices data between a given time period. | `meter_id` | query | string | No | Meter ID for price data. Deprecated - use context_entities instead. | | `from` | query | string (date-time) | Yes | Start date for price data (ISO 8601 format). | | `to` | query | string (date-time) | Yes | End date for price data (ISO 8601 format). | -| `interval` | query | "PT15M" \| "PT1H" \| "P1D" \| "P1M" | Yes | Interval between price data points (e.g., PT15M for 15 minutes, PT1H for hourly). Not all intervals have to be supported. | +| `interval` | query | "PT15M" \| "PT1H" \| "P1D" \| "P1M" \| "P1Y" | Yes | Interval between price data points (e.g., PT15M for 15 minutes, PT1H for hourly). Not all intervals have to be supported. | | `context_entities` | query | object[] | No | Additional entities to include in the context for variable interpolation in the hook. | **Sample Call** @@ -2075,6 +2491,43 @@ epilot customer-portal getPublicPortalConfig -p org_id=12324 -p origin=example - ```json { + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -2108,7 +2561,8 @@ epilot customer-portal getPublicPortalConfig -p org_id=12324 -p origin=example - }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -2130,7 +2584,8 @@ epilot customer-portal getPublicPortalConfig -p org_id=12324 -p origin=example - "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -2254,6 +2709,15 @@ epilot customer-portal getPublicPortalConfig -p org_id=12324 -p origin=example - "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": { @@ -2317,6 +2781,43 @@ epilot customer-portal getOrgPortalConfig -p origin=example --jsonata '$' ```json { + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -2350,7 +2851,8 @@ epilot customer-portal getOrgPortalConfig -p origin=example --jsonata '$' }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -2372,7 +2874,8 @@ epilot customer-portal getOrgPortalConfig -p origin=example --jsonata '$' "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -2496,6 +2999,15 @@ epilot customer-portal getOrgPortalConfig -p origin=example --jsonata '$' "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": { @@ -2522,7 +3034,8 @@ epilot customer-portal getOrgPortalConfig -p origin=example --jsonata '$' "mobile_oidc_config": {}, "provider_type": "OIDC", "attribute_mappings": {}, - "entity_matching": {} + "entity_matching": {}, + "expose_client_secret": false } ], "certificate_details": { @@ -2568,6 +3081,43 @@ epilot customer-portal getPublicPortalConfigV3 -p org_id=12324 -p portal_id=453a ```json { + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -2601,7 +3151,8 @@ epilot customer-portal getPublicPortalConfigV3 -p org_id=12324 -p portal_id=453a }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -2623,7 +3174,8 @@ epilot customer-portal getPublicPortalConfigV3 -p org_id=12324 -p portal_id=453a "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -2747,6 +3299,15 @@ epilot customer-portal getPublicPortalConfigV3 -p org_id=12324 -p portal_id=453a "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": { @@ -2810,6 +3371,43 @@ epilot customer-portal getOrgPortalConfigV3 -p portal_id=453ad7bf-86d5-46c8-8252 ```json { + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -2843,7 +3441,8 @@ epilot customer-portal getOrgPortalConfigV3 -p portal_id=453ad7bf-86d5-46c8-8252 }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -2865,7 +3464,8 @@ epilot customer-portal getOrgPortalConfigV3 -p portal_id=453ad7bf-86d5-46c8-8252 "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -2989,6 +3589,15 @@ epilot customer-portal getOrgPortalConfigV3 -p portal_id=453ad7bf-86d5-46c8-8252 "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [ + { + "trigger_type": "entity_created", + "entity_schema": "opportunity", + "enabled": true, + "template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + ], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": { @@ -3015,7 +3624,8 @@ epilot customer-portal getOrgPortalConfigV3 -p portal_id=453ad7bf-86d5-46c8-8252 "mobile_oidc_config": {}, "provider_type": "OIDC", "attribute_mappings": {}, - "entity_matching": {} + "entity_matching": {}, + "expose_client_secret": false } ], "certificate_details": { @@ -3054,6 +3664,7 @@ epilot customer-portal getAllPortalConfigs --jsonata 'data' { "data": [ { + "mobile_config": {}, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -3096,6 +3707,8 @@ epilot customer-portal getAllPortalConfigs --jsonata 'data' "origin": "string", "pages": {}, "global_blocks": {}, + "notification_triggers": [], + "engagement_center_enabled": true, "id": 12345, "organization_id": 12345, "org_settings": {}, @@ -3250,35 +3863,31 @@ epilot customer-portal upsertEmailTemplates -p origin=example --jsonata 'message --- -### `getEmailTemplatesByPortalId` - -Retrieves the email templates of a portal by portal ID +### `migrateEmailTemplateReferences` -`GET /v3/portal/email-templates/{portal_id}` +Walk every email-template config row in the caller's org and re-point any -**Parameters** +`POST /v3/portal/email-templates:migrate-references` -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `portal_id` | path | string | Yes | ID of the portal | +**Request Body** (required) **Sample Call** ```bash -epilot customer-portal getEmailTemplatesByPortalId \ - -p portal_id=123e4567-e89b-12d3-a456-426614174000 +epilot customer-portal migrateEmailTemplateReferences \ + -d '{"source_template_id":"string","destination_template_id":"string"}' ``` -Using positional args for path parameters: +Using stdin pipe: ```bash -epilot customer-portal getEmailTemplatesByPortalId 123e4567-e89b-12d3-a456-426614174000 +cat body.json | epilot customer-portal migrateEmailTemplateReferences ``` With JSONata filter: ```bash -epilot customer-portal getEmailTemplatesByPortalId -p portal_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'confirmAccount' +epilot customer-portal migrateEmailTemplateReferences --jsonata 'migrated_portal_count' ```
@@ -3286,21 +3895,9 @@ epilot customer-portal getEmailTemplatesByPortalId -p portal_id=123e4567-e89b-12 ```json { - "confirmAccount": "5da0a718-c822-403d-9f5d-20d4584e0528", - "advancedAuth": "5da0a718-c822-403d-9f5d-20d4584e0528", - "advancedMFA": "5da0a718-c822-403d-9f5d-20d4584e0528", - "journeySignUp": "5da0a718-c822-403d-9f5d-20d4584e0528", - "journeySignInOneTimePassword": "5da0a718-c822-403d-9f5d-20d4584e0528", - "journeyLoginOTP": "5da0a718-c822-403d-9f5d-20d4584e0528", - "forgotPassword": "5da0a718-c822-403d-9f5d-20d4584e0528", - "invitation": "5da0a718-c822-403d-9f5d-20d4584e0528", - "partnerInvitation": "5da0a718-c822-403d-9f5d-20d4584e0528", - "onNewQuote": "5da0a718-c822-403d-9f5d-20d4584e0528", - "onMapAPendingUser": "5da0a718-c822-403d-9f5d-20d4584e0528", - "onDocUpload": "5da0a718-c822-403d-9f5d-20d4584e0528", - "onWorkflowStepAssigned": "5da0a718-c822-403d-9f5d-20d4584e0528", - "confirmEmailUpdate": "5da0a718-c822-403d-9f5d-20d4584e0528", - "verifyCodeToSetPassword": "5da0a718-c822-403d-9f5d-20d4584e0528" + "migrated_portal_count": 2, + "migrated_portal_ids": ["string"], + "failed_portal_ids": ["string"] } ``` @@ -3308,28 +3905,131 @@ epilot customer-portal getEmailTemplatesByPortalId -p portal_id=123e4567-e89b-12 --- -### `upsertEmailTemplatesByPortalId` - -Upserts the email templates of a portal by portal ID - -`POST /v3/portal/email-templates/{portal_id}` +### `listEmailTemplateReferences` -**Parameters** +Read-only sibling of migrateEmailTemplateReferences. Lists every portal in -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `portal_id` | path | string | Yes | ID of the portal | +`POST /v3/portal/email-templates:list-references` **Request Body** (required) **Sample Call** ```bash -epilot customer-portal upsertEmailTemplatesByPortalId \ - -p portal_id=123e4567-e89b-12d3-a456-426614174000 +epilot customer-portal listEmailTemplateReferences \ + -d '{"template_id":"string"}' ``` -With request body: +Using stdin pipe: + +```bash +cat body.json | epilot customer-portal listEmailTemplateReferences +``` + +With JSONata filter: + +```bash +epilot customer-portal listEmailTemplateReferences --jsonata 'portals' +``` + +
+Sample Response + +```json +{ + "portals": [ + { + "id": "string", + "name": "string" + } + ] +} +``` + +
+ +--- + +### `getEmailTemplatesByPortalId` + +Retrieves the email templates of a portal by portal ID + +`GET /v3/portal/email-templates/{portal_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `portal_id` | path | string | Yes | ID of the portal | + +**Sample Call** + +```bash +epilot customer-portal getEmailTemplatesByPortalId \ + -p portal_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot customer-portal getEmailTemplatesByPortalId 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot customer-portal getEmailTemplatesByPortalId -p portal_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'confirmAccount' +``` + +
+Sample Response + +```json +{ + "confirmAccount": "5da0a718-c822-403d-9f5d-20d4584e0528", + "advancedAuth": "5da0a718-c822-403d-9f5d-20d4584e0528", + "advancedMFA": "5da0a718-c822-403d-9f5d-20d4584e0528", + "journeySignUp": "5da0a718-c822-403d-9f5d-20d4584e0528", + "journeySignInOneTimePassword": "5da0a718-c822-403d-9f5d-20d4584e0528", + "journeyLoginOTP": "5da0a718-c822-403d-9f5d-20d4584e0528", + "forgotPassword": "5da0a718-c822-403d-9f5d-20d4584e0528", + "invitation": "5da0a718-c822-403d-9f5d-20d4584e0528", + "partnerInvitation": "5da0a718-c822-403d-9f5d-20d4584e0528", + "onNewQuote": "5da0a718-c822-403d-9f5d-20d4584e0528", + "onMapAPendingUser": "5da0a718-c822-403d-9f5d-20d4584e0528", + "onDocUpload": "5da0a718-c822-403d-9f5d-20d4584e0528", + "onWorkflowStepAssigned": "5da0a718-c822-403d-9f5d-20d4584e0528", + "confirmEmailUpdate": "5da0a718-c822-403d-9f5d-20d4584e0528", + "verifyCodeToSetPassword": "5da0a718-c822-403d-9f5d-20d4584e0528" +} +``` + +
+ +--- + +### `upsertEmailTemplatesByPortalId` + +Upserts the email templates of a portal by portal ID + +`POST /v3/portal/email-templates/{portal_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `portal_id` | path | string | Yes | ID of the portal | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot customer-portal upsertEmailTemplatesByPortalId \ + -p portal_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With request body: ```bash epilot customer-portal upsertEmailTemplatesByPortalId \ @@ -4366,7 +5066,8 @@ epilot customer-portal checkContactExists \ "contract": { "contract_number": "123456" } - } + }, + "trigger_identifiers_check": true }' ``` @@ -4388,7 +5089,8 @@ epilot customer-portal checkContactExists -p origin=example --jsonata 'exists' ```json { "exists": true, - "contactId": "5da0a718-c822-403d-9f5d-20d4584e0528" + "contactId": "5da0a718-c822-403d-9f5d-20d4584e0528", + "reason": "TIMEOUT" } ``` @@ -4430,7 +5132,8 @@ epilot customer-portal checkContactExistsV3 \ "contract": { "contract_number": "123456" } - } + }, + "trigger_identifiers_check": true }' ``` @@ -4453,7 +5156,8 @@ epilot customer-portal checkContactExistsV3 --jsonata 'exists' { "exists": true, "contactId": "5da0a718-c822-403d-9f5d-20d4584e0528", - "accountId": "5da0a718-c822-403d-9f5d-20d4584e0528" + "accountId": "5da0a718-c822-403d-9f5d-20d4584e0528", + "reason": "TIMEOUT" } ``` @@ -4734,6 +5438,46 @@ epilot customer-portal updatePortalUserEmail --jsonata 'message' --- +### `changePortalUserPassword` + +Hand over a password change to the third-party system configured via the `changePassword` portal extension hook. + +`PUT /v2/portal/user/change/password` + +**Request Body** + +**Sample Call** + +```bash +epilot customer-portal changePortalUserPassword \ + -d '{"new_password":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot customer-portal changePortalUserPassword +``` + +With JSONata filter: + +```bash +epilot customer-portal changePortalUserPassword --jsonata 'message' +``` + +
+Sample Response + +```json +{ + "message": "string" +} +``` + +
+ +--- + ### `resendConfirmationEmail` Resend confirmation email @@ -5194,6 +5938,182 @@ epilot customer-portal postOrderAcceptance -p id=5da0a718-c822-403d-9f5d-20d4584 --- +### `getContractWithTemplates` + +Resolve Handlebars templates against a contract's related meters and return the contract with templates_output populated + +`POST /v2/portal/contract/{id}/resolve-templates` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | The ID of the contract | + +**Request Body** + +**Sample Call** + +```bash +epilot customer-portal getContractWithTemplates \ + -p id=5da0a718-c822-403d-9f5d-20d4584e0528 +``` + +With request body: + +```bash +epilot customer-portal getContractWithTemplates \ + -p id=5da0a718-c822-403d-9f5d-20d4584e0528 \ + -d '{ + "templates": {}, + "templates_ref": { + "page_id": "string", + "block_id": "string", + "config_id": "string", + "global_search_config_id": "string" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot customer-portal getContractWithTemplates 5da0a718-c822-403d-9f5d-20d4584e0528 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot customer-portal getContractWithTemplates -p id=5da0a718-c822-403d-9f5d-20d4584e0528 +``` + +With JSONata filter: + +```bash +epilot customer-portal getContractWithTemplates -p id=5da0a718-c822-403d-9f5d-20d4584e0528 --jsonata 'entity._title' +``` + +
+Sample Response + +```json +{ + "entity": { + "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", + "_title": "Example Entity", + "_org": "123", + "_tags": ["example", "mock"], + "_created_at": "2021-02-09T12:41:43.662Z", + "_updated_at": "2021-02-09T12:41:43.662Z", + "templates_output_highlighted": {}, + "search_snippets": [ + {} + ], + "contract_name": "Grid Contract", + "contract_number": "12345", + "status": "approved", + "description": "This contract is for the supply of widgets.", + "account_number": "67890", + "branch": "power", + "billing_address": "123 Main St, Anytown", + "delivery_address": "456 Elm St, Anytown", + "additional_addresses": "789 Oak St, Anytown", + "termination_date": "2022-01-01", + "termination_reason": "Non-payment", + "billing_period": "monthly", + "billing_duration_amount": 30, + "renewal_duration_amount": 365, + "renewal_duration_unit": "years", + "notice_time_amount": 30, + "notice_time_unit": "months", + "start_date": "2021-01-01", + "billing_due_day": 2, + "installment_amount": 10050, + "balance": 8990, + "balance_currency": "EUR" + }, + "orders": [ + { + "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", + "_title": "Example Entity", + "_org": "123", + "_tags": ["example", "mock"], + "_created_at": "2021-02-09T12:41:43.662Z", + "_updated_at": "2021-02-09T12:41:43.662Z", + "templates_output_highlighted": {}, + "search_snippets": [], + "_schema": "order" + } + ], + "meters": [ + { + "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", + "_title": "Example Entity", + "_org": "123", + "_tags": ["example", "mock"], + "_created_at": "2021-02-09T12:41:43.662Z", + "_updated_at": "2021-02-09T12:41:43.662Z", + "templates_output_highlighted": {}, + "search_snippets": [], + "_schema": "meter", + "templates_output": {} + } + ], + "files": [ + { + "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", + "_title": "Example Entity", + "_org": "123", + "_tags": ["example", "mock"], + "_created_at": "2021-02-09T12:41:43.662Z", + "_updated_at": "2021-02-09T12:41:43.662Z", + "templates_output_highlighted": {}, + "search_snippets": [], + "_schema": "file" + } + ], + "relations": [ + { + "_id": "5da0a718-c822-403d-9f5d-20d4584e0528", + "_title": "Example Entity", + "_org": "123", + "_tags": ["example", "mock"], + "_created_at": "2021-02-09T12:41:43.662Z", + "_updated_at": "2021-02-09T12:41:43.662Z", + "templates_output_highlighted": {}, + "search_snippets": [], + "templates_output": {}, + "_schema": "contact" + } + ], + "workflow": [ + { + "id": "8gja72h6kas6h", + "name": "Lead Qualification", + "trigger": "MANUAL", + "status": "STARTED", + "creationTime": "2021-04-27T12:01:13.000Z", + "lastUpdateTime": "2021-04-27T12:01:13.000Z", + "dueDate": "2021-04-27T12:01:13.000Z", + "assignedTo": ["252", "29052"], + "flow": [] + } + ], + "journey_actions": [ + { + "journey_id": "string", + "action_label": {}, + "slug": "string", + "rules": [] + } + ] +} +``` + +
+ +--- + ### `addContractByIdentifiers` Self-assign contract(s) by pre-configured identifiers. @@ -5800,6 +6720,53 @@ epilot customer-portal getRegistrationIdentifiers --jsonata 'data' --- +### `getFilePreview` + +resolves an in-portal preview for a file. Returns a Content-Disposition: inline URL for directly-previewable files (PDF, + +`GET /v2/portal/user/file/{id}/preview` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | The Id of a file | + +**Sample Call** + +```bash +epilot customer-portal getFilePreview \ + -p id=5da0a718-c822-403d-9f5d-20d4584e0528 +``` + +Using positional args for path parameters: + +```bash +epilot customer-portal getFilePreview 5da0a718-c822-403d-9f5d-20d4584e0528 +``` + +With JSONata filter: + +```bash +epilot customer-portal getFilePreview -p id=5da0a718-c822-403d-9f5d-20d4584e0528 --jsonata 'kind' +``` + +
+Sample Response + +```json +{ + "kind": "pdf", + "url": "https://example.com/path", + "requires_auth": true, + "download_url": "https://example.com/path" +} +``` + +
+ +--- + ### `trackFileDownloaded` Track that user has downloaded a file @@ -5864,7 +6831,8 @@ epilot customer-portal trackFileDownloaded -p id=5da0a718-c822-403d-9f5d-20d4584 "_title": "Opportunity ABC" } ], - "is_new": true + "is_new": true, + "custom_download_url_auth": "token" } } ``` @@ -6252,6 +7220,12 @@ epilot customer-portal getPortalUserEntity \ "subtitle": "{{contract.contract_number}}" } }, + "templates_ref": { + "page_id": "string", + "block_id": "string", + "config_id": "string", + "global_search_config_id": "string" + }, "filters": [ { "term": { @@ -6364,6 +7338,12 @@ epilot customer-portal searchPortalUserEntities \ "main_content_name": "{{contract.contract_name}} ({{contract.contract_number}})", "content_bottom_name": "{{custom_contract_delivery_address}}" }, + "templates_ref": { + "page_id": "string", + "block_id": "string", + "config_id": "string", + "global_search_config_id": "string" + }, "filters": [ { "term": { @@ -6668,27 +7648,288 @@ epilot customer-portal getEntityWorkflows -p slug=contact -p id=abc123 --jsonata "assignedTo": ["252", "29052"], "flow": [ { - "id": "sectionId1", - "name": "Initial Information Gathering", - "steps": [ + "id": "sectionId1", + "name": "Initial Information Gathering", + "steps": [ + { + "id": "sada5641f3a21", + "name": "Call client and confirm address and product", + "status": "ASSIGNED", + "assignedTo": ["11"] + }, + { + "id": "sada5641f3a22", + "name": "Check product availability", + "status": "UNASSIGNED" + } + ] + }, + { + "id": "firstLevelStepId1", + "name": "Print and send catalog", + "status": "SKIPPED", + "dueDate": "2023-01-15T20:00:00" + } + ] + } + ] +} +``` + +
+ +--- + +### `getOutstandingTasks` + +Returns all outstanding workflow journey tasks for the authenticated portal user, across their opportunity, order and co + +`GET /v2/portal/engagement/tasks` + +**Sample Call** + +```bash +epilot customer-portal getOutstandingTasks +``` + +With JSONata filter: + +```bash +epilot customer-portal getOutstandingTasks --jsonata 'tasks' +``` + +
+Sample Response + +```json +{ + "tasks": [ + { + "entity_id": "string", + "entity_schema": "string", + "entity_title": "string", + "workflow_id": "string", + "workflow_name": "string", + "step_id": "string", + "step_name": "string", + "journey_id": "string", + "complete_task_automatically": true + } + ], + "total": 0 +} +``` + +
+ +--- + +### `getEntityPortalWorkflows` + +Get all portal-relevant workflows associated with an entity (requires access to the entity), + +`GET /v2/portal/entity/{slug}/{id}/workflows/linearized` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | string | Yes | | +| `id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot customer-portal getEntityPortalWorkflows \ + -p slug=opportunity \ + -p id=abc123 +``` + +Using positional args for path parameters: + +```bash +epilot customer-portal getEntityPortalWorkflows opportunity abc123 +``` + +With JSONata filter: + +```bash +epilot customer-portal getEntityPortalWorkflows -p slug=opportunity -p id=abc123 --jsonata 'portal_workflows' +``` + +
+Sample Response + +```json +{ + "portal_workflows": [ + { + "id": "string", + "definition_id": "string", + "name": "string", + "status": "STARTED", + "version": 2, + "created_at": "string", + "updated_at": "string", + "completed_at": "string", + "due_date": "string", + "assigned_to": ["string"], + "contexts": [ + { + "entity_id": "string", + "entity_schema": "string", + "is_primary": true + } + ], + "is_path_complete": true, + "tasks": [ + { + "id": "string", + "name": "string", + "order": 0, + "status": "COMPLETED", + "is_active": true, + "ecp": { + "enabled": true, + "label": "string", + "description": "string", + "journey": { + "id": "string", + "journeyId": "string", + "name": "string", + "complete_task_automatically": true + } + }, + "installer": { + "enabled": true, + "label": "string", + "description": "string", + "journey": { + "id": "string", + "journeyId": "string", + "name": "string", + "complete_task_automatically": true + } + }, + "journey": { + "id": "string", + "journeyId": "string", + "name": "string", + "complete_task_automatically": true + }, + "assigned_to": ["string"], + "phase_id": "string", + "phase_name": "string", + "completed_at": "string", + "updated_at": "string" + } + ] + } + ] +} +``` + +
+ +--- + +### `getEntityPortalWorkflowsBatch` + +Batch variant of `getEntityPortalWorkflows`: returns portal-relevant workflows for + +`POST /v2/portal/entities/workflows/linearized/batch` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot customer-portal getEntityPortalWorkflowsBatch \ + -d '{"entities":[{"id":"string","slug":"order"}]}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot customer-portal getEntityPortalWorkflowsBatch +``` + +With JSONata filter: + +```bash +epilot customer-portal getEntityPortalWorkflowsBatch --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "entity_id": "string", + "portal_workflows": [ + { + "id": "string", + "definition_id": "string", + "name": "string", + "status": "STARTED", + "version": 2, + "created_at": "string", + "updated_at": "string", + "completed_at": "string", + "due_date": "string", + "assigned_to": ["string"], + "contexts": [ { - "id": "sada5641f3a21", - "name": "Call client and confirm address and product", - "status": "ASSIGNED", - "assignedTo": ["11"] - }, + "entity_id": "string", + "entity_schema": "string", + "is_primary": true + } + ], + "is_path_complete": true, + "tasks": [ { - "id": "sada5641f3a22", - "name": "Check product availability", - "status": "UNASSIGNED" + "id": "string", + "name": "string", + "order": 0, + "status": "COMPLETED", + "is_active": true, + "ecp": { + "enabled": true, + "label": "string", + "description": "string", + "journey": { + "id": "string", + "journeyId": "string", + "name": "string", + "complete_task_automatically": true + } + }, + "installer": { + "enabled": true, + "label": "string", + "description": "string", + "journey": { + "id": "string", + "journeyId": "string", + "name": "string", + "complete_task_automatically": true + } + }, + "journey": { + "id": "string", + "journeyId": "string", + "name": "string", + "complete_task_automatically": true + }, + "assigned_to": ["string"], + "phase_id": "string", + "phase_name": "string", + "completed_at": "string", + "updated_at": "string" } ] - }, - { - "id": "firstLevelStepId1", - "name": "Print and send catalog", - "status": "SKIPPED", - "dueDate": "2023-01-15T20:00:00" } ] } @@ -6908,9 +8149,29 @@ Fetches meter readings for a counter and optionally resolves Handlebars **Sample Call** +```bash +epilot customer-portal getMeterReadings +``` + +With request body: + ```bash epilot customer-portal getMeterReadings \ - -d '{"meter_id":"string","counter_id":"string","sort":"desc","from":0,"size":10,"templates":{},"counter_templates":{}}' + -d '{ + "meter_id": "string", + "counter_id": "string", + "sort": "desc", + "from": 0, + "size": 10, + "templates": {}, + "counter_templates": {}, + "templates_ref": { + "page_id": "string", + "block_id": "string", + "config_id": "string", + "global_search_config_id": "string" + } +}' ``` Using stdin pipe: @@ -7176,7 +8437,7 @@ epilot customer-portal ssoCallback \ "token_endpoint": "https://www.facebook.com/v12.0/dialog/oauth", "grant_type": "authorization_code", "code": "123456", - "redirect_uri": "https://customer-portal.com/login", + "redirect_uri": "https://customer-portal.com/sso", "client_id": "123456", "code_verifier": "123456" }' @@ -8376,6 +9637,147 @@ epilot customer-portal updateCampaignPortalBlockStatus -p campaign_id=123e4567-e --- +### `listPortalNotifications` + +Lists the 360 notifications addressed to the authenticated portal user, newest first. The organization and the portal us + +`GET /v2/portal/notifications` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `cursor` | query | string | No | Base64 encoded cursor returned by a previous call, used for pagination. | +| `limit` | query | number | No | The maximum number of notifications to return. | + +**Sample Call** + +```bash +epilot customer-portal listPortalNotifications +``` + +With JSONata filter: + +```bash +epilot customer-portal listPortalNotifications --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "cursor": "string", + "total_unread": 0, + "results": [ + { + "id": "1234567890", + "notification_id": 1234567890, + "type": "workflow_step_overdue", + "title": { + "en": "string", + "de": "string" + }, + "message": { + "en": "string", + "de": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "read": false, + "redirect_url": "string" + } + ] +} +``` + +
+ +--- + +### `getPortalNotificationsUnreadCount` + +Returns the number of unread notifications for the authenticated portal user. + +`GET /v2/portal/notifications/unread-count` + +**Sample Call** + +```bash +epilot customer-portal getPortalNotificationsUnreadCount +``` + +With JSONata filter: + +```bash +epilot customer-portal getPortalNotificationsUnreadCount --jsonata 'count' +``` + +
+Sample Response + +```json +{ + "count": 3 +} +``` + +
+ +--- + +### `markAllPortalNotificationsRead` + +Marks all notifications of the authenticated portal user as read. + +`PUT /v2/portal/notifications/read-all` + +**Sample Call** + +```bash +epilot customer-portal markAllPortalNotificationsRead +``` + +With JSONata filter: + +```bash +epilot customer-portal markAllPortalNotificationsRead --jsonata '$' +``` + +--- + +### `markPortalNotificationRead` + +Marks a single notification of the authenticated portal user as read. + +`PUT /v2/portal/notifications/{id}/read` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | number | Yes | Numeric id of the notification to mark as read. | + +**Sample Call** + +```bash +epilot customer-portal markPortalNotificationRead \ + -p id=1 +``` + +Using positional args for path parameters: + +```bash +epilot customer-portal markPortalNotificationRead 1 +``` + +With JSONata filter: + +```bash +epilot customer-portal markPortalNotificationRead -p id=1 --jsonata '$' +``` + +--- + ### `updateNotificationsStatus` Updates the statuses of multiple notifications at once. @@ -8561,9 +9963,47 @@ epilot customer-portal createPortalConfig \ "attribute_mappings": {}, "entity_matching": {}, "oidc_config": {}, - "mobile_oidc_config": {} + "mobile_oidc_config": {}, + "expose_client_secret": false + } + ], + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" } - ], + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -8597,7 +10037,8 @@ epilot customer-portal createPortalConfig \ }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -8619,7 +10060,8 @@ epilot customer-portal createPortalConfig \ "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -8810,9 +10252,47 @@ epilot customer-portal createPortalConfig --jsonata '$' "attribute_mappings": {}, "entity_matching": {}, "oidc_config": {}, - "mobile_oidc_config": {} + "mobile_oidc_config": {}, + "expose_client_secret": false + } + ], + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" } - ], + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -8846,7 +10326,8 @@ epilot customer-portal createPortalConfig --jsonata '$' }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -8868,7 +10349,8 @@ epilot customer-portal createPortalConfig --jsonata '$' "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -9103,9 +10585,47 @@ epilot customer-portal getPortalConfigV3 -p portal_id=5da0a718-c822-403d-9f5d-20 "attribute_mappings": {}, "entity_matching": {}, "oidc_config": {}, - "mobile_oidc_config": {} + "mobile_oidc_config": {}, + "expose_client_secret": false + } + ], + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" } - ], + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -9139,7 +10659,8 @@ epilot customer-portal getPortalConfigV3 -p portal_id=5da0a718-c822-403d-9f5d-20 }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -9161,7 +10682,8 @@ epilot customer-portal getPortalConfigV3 -p portal_id=5da0a718-c822-403d-9f5d-20 "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -9343,6 +10865,9 @@ Updates a specific portal configuration by ID. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `portal_id` | path | string (uuid) | Yes | Portal ID (readonly UUID generated on portal creation) | +| `page_upsert_mode` | query | "id" \| "slug" | No | Determines how pages are matched for upsert operations: +- `id` (default): Match pages by their ID. Use this when page IDs are stable and known upfront. +- `slug`: Match pages by their slug. When a requ | **Request Body** (required) @@ -9387,9 +10912,47 @@ epilot customer-portal putPortalConfig \ "attribute_mappings": {}, "entity_matching": {}, "oidc_config": {}, - "mobile_oidc_config": {} + "mobile_oidc_config": {}, + "expose_client_secret": false + } + ], + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" } - ], + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -9423,7 +10986,8 @@ epilot customer-portal putPortalConfig \ }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -9445,7 +11009,8 @@ epilot customer-portal putPortalConfig \ "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -9663,9 +11228,47 @@ epilot customer-portal putPortalConfig -p portal_id=5da0a718-c822-403d-9f5d-20d4 "attribute_mappings": {}, "entity_matching": {}, "oidc_config": {}, - "mobile_oidc_config": {} + "mobile_oidc_config": {}, + "expose_client_secret": false + } + ], + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" } - ], + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -9699,7 +11302,8 @@ epilot customer-portal putPortalConfig -p portal_id=5da0a718-c822-403d-9f5d-20d4 }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -9721,7 +11325,8 @@ epilot customer-portal putPortalConfig -p portal_id=5da0a718-c822-403d-9f5d-20d4 "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -9955,6 +11560,7 @@ epilot customer-portal listAllPortalConfigs --jsonata 'data' "extension_hooks": {}, "default_user_to_notify": {}, "identity_providers": [], + "mobile_config": {}, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -10031,7 +11637,7 @@ epilot customer-portal swapPortalConfig \ -d '{ "source_portal_id": "453ad7bf-86d5-46c8-8252-bcc868df5e3c", "target_portal_id": "453ad7bf-86d5-46c8-8252-bcc868df5e3c", - "items_to_swap": ["all"] + "items_to_swap": ["email_templates"] }' ``` @@ -10120,9 +11726,47 @@ epilot customer-portal clonePortalConfig --jsonata '$' "attribute_mappings": {}, "entity_matching": {}, "oidc_config": {}, - "mobile_oidc_config": {} + "mobile_oidc_config": {}, + "expose_client_secret": false + } + ], + "mobile_config": { + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": {} + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": {} + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" } - ], + }, "enabled": true, "name": "Installer Portal", "domain": "abc.com", @@ -10156,7 +11800,8 @@ epilot customer-portal clonePortalConfig --jsonata '$' }, "entry_point": "PASSWORD", "preferred_sso_providers": ["office-365-login"], - "auto_redirect_to_sso": true + "auto_redirect_to_sso": true, + "prevent_user_enumeration": true }, "cognito_details": { "cognito_user_pool_client_id": "6bsd0jkgoie74k2i8mrhc1vest", @@ -10178,7 +11823,8 @@ epilot customer-portal clonePortalConfig --jsonata '$' "require_lowercase": true, "require_uppercase": true, "require_numbers": true, - "require_symbols": true + "require_symbols": true, + "password_history_size": 3 } }, "config": "string", @@ -10359,20 +12005,9 @@ Invites a partner to a portal **Sample Call** -```bash -epilot customer-portal invitePartner -``` - -With request body: - ```bash epilot customer-portal invitePartner \ - -d '{ - "email": "string", - "represents_contact_list": ["5da0a718-c822-403d-9f5d-20d4584e0528"], - "contact_data": {}, - "portal_user_data": {} -}' + -d '{"email":"string","contact_data":{},"portal_user_data":{}}' ``` Using stdin pipe: @@ -10697,3 +12332,219 @@ epilot customer-portal portalProxyExecute --jsonata 'data'
--- + +### `getMobileConfig` + +Returns the portal's mobile app configuration. By default the response is build-ready (resolved): base info (display_nam + +`GET /v1/portal/mobile-config` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `portal_id` | query | string | Yes | Portal ID | +| `raw` | query | boolean | No | Return only the stored mobile_config without resolving base info/branding. | + +**Sample Call** + +```bash +epilot customer-portal getMobileConfig \ + -p portal_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot customer-portal getMobileConfig -p portal_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'portal_id' +``` + +
+Sample Response + +```json +{ + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": { + "version": "string", + "build_number": 0, + "track": "string", + "status": "building", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" + } + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": { + "version": "string", + "build_number": 0, + "track": "string", + "status": "building", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" + } + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } +} +``` + +
+ +--- + +### `putMobileConfig` + +Merges the provided fields into the portal's mobile app configuration + +`PUT /v1/portal/mobile-config` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `portal_id` | query | string | Yes | Portal ID | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot customer-portal putMobileConfig \ + -p portal_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With request body: + +```bash +epilot customer-portal putMobileConfig \ + -p portal_id=123e4567-e89b-12d3-a456-426614174000 \ + -d '{ + "enabled": true, + "ios": { + "bundle_id": "string", + "team_id": "string", + "store_url": "string", + "app_store_id": "string" + }, + "android": { + "package_name": "string", + "store_url": "string" + }, + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot customer-portal putMobileConfig -p portal_id=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot customer-portal putMobileConfig -p portal_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'portal_id' +``` + +
+Sample Response + +```json +{ + "portal_id": "string", + "enabled": true, + "display_name": "string", + "app_host": "string", + "environment": "prod", + "branding": { + "app_icon": "string", + "splash": "string", + "splash_dark": "string", + "icon_background_color": "string", + "splash_background_color": "string", + "splash_background_color_dark": "string" + }, + "ios": { + "bundle_id": "string", + "team_id": "string", + "credentials_status": "not_configured", + "app_store_id": "string", + "store_url": "string", + "last_build": { + "version": "string", + "build_number": 0, + "track": "string", + "status": "building", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" + } + }, + "android": { + "package_name": "string", + "credentials_status": "not_configured", + "upload_key_status": "not_configured", + "store_url": "string", + "last_build": { + "version": "string", + "build_number": 0, + "track": "string", + "status": "building", + "updated_at": "1970-01-01T00:00:00.000Z", + "error": "string" + } + }, + "ota": { + "enabled": true, + "channel": "canary", + "auto_update": true, + "update_strategy": "next-launch", + "min_native_version": "string" + } +} +``` + +
+ +--- diff --git a/packages/cli/docs/dashboard.md b/packages/cli/docs/dashboard.md index 44d94f85a..fac1d649e 100644 --- a/packages/cli/docs/dashboard.md +++ b/packages/cli/docs/dashboard.md @@ -38,10 +38,23 @@ epilot dashboard listDashboards **Dashboards** - [`listDashboards`](#listdashboards) — List dashboards available to the user - [`createDashboard`](#createdashboard) — Create new dashboard +- [`listFavoriteDashboardIds`](#listfavoritedashboardids) — Returns the current user's favorited dashboard ids, with no dashboard metadata. Lets a +- [`favoriteDashboard`](#favoritedashboard) — Marks the dashboard as favorited by the current user. Idempotent — favoriting an +- [`unfavoriteDashboard`](#unfavoritedashboard) — Removes the current user's favorite for the dashboard. Idempotent — unfavoriting a - [`getDashboard`](#getdashboard) — Get dashboard by ID - [`putDashboard`](#putdashboard) — Update a dashboard by ID +- [`patchDashboard`](#patchdashboard) — Partially update a dashboard by ID. Update content (title/tiles) and/or manage sharing - [`deleteDashboard`](#deletedashboard) — Delete a dashboard by ID +**Insights** +- [`listInsights`](#listinsights) — List insights (saved charts) available to the user +- [`createInsight`](#createinsight) — Create a new insight (saved chart). The caller becomes the owner. +- [`listInsightTags`](#listinsighttags) — List the distinct tags used by insights in the organization (for filter facets) +- [`getInsight`](#getinsight) — Get insight by ID +- [`putInsight`](#putinsight) — Replace an insight's content by ID. Sharing is managed via patchInsight. +- [`patchInsight`](#patchinsight) — Partially update an insight by ID. Update content (name/description/visualisation/tags) and/or +- [`deleteInsight`](#deleteinsight) — Delete an insight by ID. Only owners may delete. + **Visualisations** - [`listAvailableVisualisations`](#listavailablevisualisations) — Returns list of available Visualisations to configure new dashboard tiles @@ -54,6 +67,31 @@ List dashboards available to the user `GET /v1/dashboard/dashboards` +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `q` | query | string | No | Free-text search over name/title and description (case-insensitive substring). | +| `created_by` | query | string | No | Filter by the id of the creating user. | +| `created_after` | query | string (date-time) | No | | +| `created_before` | query | string (date-time) | No | | +| `updated_after` | query | string (date-time) | No | | +| `updated_before` | query | string (date-time) | No | | +| `shared_with` | query | string[] | No | Filter to resources shared with any of the given user ids. | +| `owner` | query | string[] | No | Filter to resources owned by any of the given user ids. | +| `accessible_to` | query | string | No | Filter to resources the given user id may view (owner, shared, org-wide or legacy). | +| `favorite` | query | boolean | No | Dashboards only. Filter to only (`true`) or exclude (`false`) the requesting user's +favorited dashboards. A user with zero favorites and `favorite=true` gets an empty page, +not the unfiltered list. + | +| `favorites_first` | query | boolean | No | Dashboards only. No filtering — sorts the requesting user's favorited dashboards first, +preserving the existing secondary ordering (`sort`/`order`) otherwise. + | +| `sort` | query | "created_at" \| "updated_at" \| "name" \| "title" | No | Field to sort by. `name` and `title` are aliases for the resource's display name and are normalised per resource: dashboards sort by `title` and insights by `name`, whichever of the two values is sent | +| `order` | query | "asc" \| "desc" | No | Sort direction. Defaults to ascending (preserving the pre-migration order). | +| `limit` | query | number | No | Maximum results to return (max 200). Omit to return all matching results (pagination is opt-in). | +| `offset` | query | number | No | Number of results to skip, for pagination. Use with limit (page N = offset N*limit). | + **Sample Call** ```bash @@ -73,42 +111,37 @@ epilot dashboard listDashboards --jsonata 'results[0]' { "results": [ { + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Employee Dashboard", "tiles": [ { "id": "e4af1297-1fd6-440f-9846-f475f580d40f", "coordinates": {}, - "visualisation": "timechart", - "visualisation_config": { - "query": { - "dataset": "entity_operations", - "measure": "count_operations", - "filters": [ - { - "operation": ["createEntity"] - }, - { - "entity_schema": ["opportunity"] - } - ], - "dimensions": [ - { - "time_with_granularity": "month" - }, - { - "entity_attribute": "source.title" - } - ] - }, - "options": { - "type": "line" - } - } + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" } - ] + ], + "favorited": false } - ] + ], + "pagination": { + "total": 0, + "limit": 0, + "offset": 0, + "has_more": true + } } ``` @@ -135,40 +168,29 @@ With request body: ```bash epilot dashboard createDashboard \ -d '{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Employee Dashboard", "tiles": [ { "id": "e4af1297-1fd6-440f-9846-f475f580d40f", "coordinates": {}, - "visualisation": "timechart", - "visualisation_config": { - "query": { - "dataset": "entity_operations", - "measure": "count_operations", - "filters": [ - { - "operation": ["createEntity"] - }, - { - "entity_schema": ["opportunity"] - } - ], - "dimensions": [ - { - "time_with_granularity": "month" - }, - { - "entity_attribute": "source.title" - } - ] - }, - "options": { - "type": "line" - } - } + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" } - ] + ], + "favorited": false }' ``` @@ -181,7 +203,7 @@ cat body.json | epilot dashboard createDashboard With JSONata filter: ```bash -epilot dashboard createDashboard --jsonata 'id' +epilot dashboard createDashboard --jsonata '$' ```
@@ -189,40 +211,61 @@ epilot dashboard createDashboard --jsonata 'id' ```json { + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Employee Dashboard", "tiles": [ { "id": "e4af1297-1fd6-440f-9846-f475f580d40f", "coordinates": {}, - "visualisation": "timechart", - "visualisation_config": { - "query": { - "dataset": "entity_operations", - "measure": "count_operations", - "filters": [ - { - "operation": ["createEntity"] - }, - { - "entity_schema": ["opportunity"] - } - ], - "dimensions": [ - { - "time_with_granularity": "month" - }, - { - "entity_attribute": "source.title" - } - ] - }, - "options": { - "type": "line" - } - } + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" } - ] + ], + "favorited": false +} +``` + +
+ +--- + +### `listFavoriteDashboardIds` + +Returns the current user's favorited dashboard ids, with no dashboard metadata. Lets a + +`GET /v1/dashboard/dashboards/favorites` + +**Sample Call** + +```bash +epilot dashboard listFavoriteDashboardIds +``` + +With JSONata filter: + +```bash +epilot dashboard listFavoriteDashboardIds --jsonata 'dashboard_ids' +``` + +
+Sample Response + +```json +{ + "dashboard_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "total": 0 } ``` @@ -230,6 +273,72 @@ epilot dashboard createDashboard --jsonata 'id' --- +### `favoriteDashboard` + +Marks the dashboard as favorited by the current user. Idempotent — favoriting an + +`PUT /v1/dashboard/dashboards/{id}/favorite` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | | + +**Sample Call** + +```bash +epilot dashboard favoriteDashboard \ + -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +Using positional args for path parameters: + +```bash +epilot dashboard favoriteDashboard 3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +With JSONata filter: + +```bash +epilot dashboard favoriteDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata '$' +``` + +--- + +### `unfavoriteDashboard` + +Removes the current user's favorite for the dashboard. Idempotent — unfavoriting a + +`DELETE /v1/dashboard/dashboards/{id}/favorite` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | | + +**Sample Call** + +```bash +epilot dashboard unfavoriteDashboard \ + -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +Using positional args for path parameters: + +```bash +epilot dashboard unfavoriteDashboard 3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +With JSONata filter: + +```bash +epilot dashboard unfavoriteDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata '$' +``` + +--- + ### `getDashboard` Get dashboard by ID @@ -258,7 +367,7 @@ epilot dashboard getDashboard 3fa85f64-5717-4562-b3fc-2c963f66afa6 With JSONata filter: ```bash -epilot dashboard getDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata 'id' +epilot dashboard getDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata '$' ```
@@ -266,40 +375,29 @@ epilot dashboard getDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsona ```json { + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Employee Dashboard", "tiles": [ { "id": "e4af1297-1fd6-440f-9846-f475f580d40f", "coordinates": {}, - "visualisation": "timechart", - "visualisation_config": { - "query": { - "dataset": "entity_operations", - "measure": "count_operations", - "filters": [ - { - "operation": ["createEntity"] - }, - { - "entity_schema": ["opportunity"] - } - ], - "dimensions": [ - { - "time_with_granularity": "month" - }, - { - "entity_attribute": "source.title" - } - ] - }, - "options": { - "type": "line" - } - } + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" } - ] + ], + "favorited": false } ``` @@ -334,40 +432,29 @@ With request body: epilot dashboard putDashboard \ -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 \ -d '{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Employee Dashboard", "tiles": [ { "id": "e4af1297-1fd6-440f-9846-f475f580d40f", "coordinates": {}, - "visualisation": "timechart", - "visualisation_config": { - "query": { - "dataset": "entity_operations", - "measure": "count_operations", - "filters": [ - { - "operation": ["createEntity"] - }, - { - "entity_schema": ["opportunity"] - } - ], - "dimensions": [ - { - "time_with_granularity": "month" - }, - { - "entity_attribute": "source.title" - } - ] - }, - "options": { - "type": "line" - } - } + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" } - ] + ], + "favorited": false }' ``` @@ -386,7 +473,7 @@ cat body.json | epilot dashboard putDashboard -p id=3fa85f64-5717-4562-b3fc-2c96 With JSONata filter: ```bash -epilot dashboard putDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata 'id' +epilot dashboard putDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata '$' ```
@@ -394,40 +481,29 @@ epilot dashboard putDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsona ```json { + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "title": "Employee Dashboard", "tiles": [ { "id": "e4af1297-1fd6-440f-9846-f475f580d40f", "coordinates": {}, - "visualisation": "timechart", - "visualisation_config": { - "query": { - "dataset": "entity_operations", - "measure": "count_operations", - "filters": [ - { - "operation": ["createEntity"] - }, - { - "entity_schema": ["opportunity"] - } - ], - "dimensions": [ - { - "time_with_granularity": "month" - }, - { - "entity_attribute": "source.title" - } - ] - }, - "options": { - "type": "line" - } - } + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" } - ] + ], + "favorited": false } ``` @@ -435,11 +511,11 @@ epilot dashboard putDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsona --- -### `deleteDashboard` +### `patchDashboard` -Delete a dashboard by ID +Partially update a dashboard by ID. Update content (title/tiles) and/or manage sharing -`DELETE /v1/dashboard/dashboards/{id}` +`PATCH /v1/dashboard/dashboards/{id}` **Parameters** @@ -447,64 +523,844 @@ Delete a dashboard by ID | ---- | -- | ---- | -------- | ----------- | | `id` | path | string (uuid) | Yes | | +**Request Body** + **Sample Call** ```bash -epilot dashboard deleteDashboard \ +epilot dashboard patchDashboard \ -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 ``` -Using positional args for path parameters: - -```bash -epilot dashboard deleteDashboard 3fa85f64-5717-4562-b3fc-2c963f66afa6 -``` - -With JSONata filter: +With request body: ```bash -epilot dashboard deleteDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata 'id' -``` - -
-Sample Response - -```json -{ - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", +epilot dashboard patchDashboard \ + -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 \ + -d '{ + "owners": ["string"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", "title": "Employee Dashboard", "tiles": [ { "id": "e4af1297-1fd6-440f-9846-f475f580d40f", "coordinates": {}, - "visualisation": "timechart", + "title": "Number of opportunities created by journeys every month", + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "visualisation_id": "timechart", "visualisation_config": { "query": { "dataset": "entity_operations", "measure": "count_operations", - "filters": [ - { - "operation": ["createEntity"] - }, + "dimensions": [ { - "entity_schema": ["opportunity"] + "time_with_granularity": "year-month" } ], - "dimensions": [ - { - "time_with_granularity": "month" - }, + "filters": [ { - "entity_attribute": "source.title" + "entity_schema": "opportunity" } ] }, "options": { - "type": "line" + "type": "bar" } } } ] +}' +``` + +Using positional args for path parameters: + +```bash +epilot dashboard patchDashboard 3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot dashboard patchDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +With JSONata filter: + +```bash +epilot dashboard patchDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Employee Dashboard", + "tiles": [ + { + "id": "e4af1297-1fd6-440f-9846-f475f580d40f", + "coordinates": {}, + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" + } + ], + "favorited": false +} +``` + +
+ +--- + +### `deleteDashboard` + +Delete a dashboard by ID + +`DELETE /v1/dashboard/dashboards/{id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | | + +**Sample Call** + +```bash +epilot dashboard deleteDashboard \ + -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +Using positional args for path parameters: + +```bash +epilot dashboard deleteDashboard 3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +With JSONata filter: + +```bash +epilot dashboard deleteDashboard -p id=3fa85f64-5717-4562-b3fc-2c963f66afa6 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Employee Dashboard", + "tiles": [ + { + "id": "e4af1297-1fd6-440f-9846-f475f580d40f", + "coordinates": {}, + "insight_id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e" + } + ], + "favorited": false +} +``` + +
+ +--- + +### `listInsights` + +List insights (saved charts) available to the user + +`GET /v1/dashboard/insights` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `q` | query | string | No | Free-text search over name/title and description (case-insensitive substring). | +| `visualisation_id` | query | "kpi" \| "funnel" \| "toplist" \| "timechart" \| "pie" \| "bar" \| "entity_list" \| "markdown" \| "news_feed" \| "workflow"[] | No | Filter insights by one or more visualisation ids. | +| `tags` | query | string[] | No | Filter by one or more tags (see tags_match for any/all semantics). | +| `tags_match` | query | "any" \| "all" | No | Whether a result must match any (default) or all of the given tags. | +| `created_by` | query | string | No | Filter by the id of the creating user. | +| `created_after` | query | string (date-time) | No | | +| `created_before` | query | string (date-time) | No | | +| `updated_after` | query | string (date-time) | No | | +| `updated_before` | query | string (date-time) | No | | +| `shared_with` | query | string[] | No | Filter to resources shared with any of the given user ids. | +| `owner` | query | string[] | No | Filter to resources owned by any of the given user ids. | +| `accessible_to` | query | string | No | Filter to resources the given user id may view (owner, shared, org-wide or legacy). | +| `sort` | query | "created_at" \| "updated_at" \| "name" \| "title" | No | Field to sort by. `name` and `title` are aliases for the resource's display name and are normalised per resource: dashboards sort by `title` and insights by `name`, whichever of the two values is sent | +| `order` | query | "asc" \| "desc" | No | Sort direction. Defaults to ascending (preserving the pre-migration order). | +| `limit` | query | number | No | Maximum results to return (max 200). Omit to return all matching results (pagination is opt-in). | +| `offset` | query | number | No | Number of results to skip, for pagination. Use with limit (page N = offset N*limit). | + +**Sample Call** + +```bash +epilot dashboard listInsights +``` + +With JSONata filter: + +```bash +epilot dashboard listInsights --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] + } + ], + "pagination": { + "total": 0, + "limit": 0, + "offset": 0, + "has_more": true + } +} +``` + +
+ +--- + +### `createInsight` + +Create a new insight (saved chart). The caller becomes the owner. + +`POST /v1/dashboard/insights` + +**Request Body** + +**Sample Call** + +```bash +epilot dashboard createInsight +``` + +With request body: + +```bash +epilot dashboard createInsight \ + -d '{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot dashboard createInsight +``` + +With JSONata filter: + +```bash +epilot dashboard createInsight --jsonata '$' +``` + +
+Sample Response + +```json +{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] +} +``` + +
+ +--- + +### `listInsightTags` + +List the distinct tags used by insights in the organization (for filter facets) + +`GET /v1/dashboard/insights/tags` + +**Sample Call** + +```bash +epilot dashboard listInsightTags +``` + +With JSONata filter: + +```bash +epilot dashboard listInsightTags --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": ["string"] +} +``` + +
+ +--- + +### `getInsight` + +Get insight by ID + +`GET /v1/dashboard/insights/{id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | | + +**Sample Call** + +```bash +epilot dashboard getInsight \ + -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +Using positional args for path parameters: + +```bash +epilot dashboard getInsight 8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +With JSONata filter: + +```bash +epilot dashboard getInsight -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e --jsonata '$' +``` + +
+Sample Response + +```json +{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] +} +``` + +
+ +--- + +### `putInsight` + +Replace an insight's content by ID. Sharing is managed via patchInsight. + +`PUT /v1/dashboard/insights/{id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot dashboard putInsight \ + -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +With request body: + +```bash +epilot dashboard putInsight \ + -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e \ + -d '{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] +}' +``` + +Using positional args for path parameters: + +```bash +epilot dashboard putInsight 8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +Using stdin pipe: + +```bash +cat body.json | epilot dashboard putInsight -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +With JSONata filter: + +```bash +epilot dashboard putInsight -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e --jsonata '$' +``` + +
+Sample Response + +```json +{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] +} +``` + +
+ +--- + +### `patchInsight` + +Partially update an insight by ID. Update content (name/description/visualisation/tags) and/or + +`PATCH /v1/dashboard/insights/{id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot dashboard patchInsight \ + -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +With request body: + +```bash +epilot dashboard patchInsight \ + -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e \ + -d '{ + "owners": ["string"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "name": "string", + "description": "string", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] +}' +``` + +Using positional args for path parameters: + +```bash +epilot dashboard patchInsight 8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +Using stdin pipe: + +```bash +cat body.json | epilot dashboard patchInsight -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +With JSONata filter: + +```bash +epilot dashboard patchInsight -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e --jsonata '$' +``` + +
+Sample Response + +```json +{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] +} +``` + +
+ +--- + +### `deleteInsight` + +Delete an insight by ID. Only owners may delete. + +`DELETE /v1/dashboard/insights/{id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | | + +**Sample Call** + +```bash +epilot dashboard deleteInsight \ + -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +Using positional args for path parameters: + +```bash +epilot dashboard deleteInsight 8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e +``` + +With JSONata filter: + +```bash +epilot dashboard deleteInsight -p id=8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e --jsonata '$' +``` + +
+Sample Response + +```json +{ + "created_by": "10598", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_by": "10598", + "updated_at": "1970-01-01T00:00:00.000Z", + "owner_org_id": "739224", + "owners": ["10598"], + "shared_with": [ + { + "user_id": "10598", + "permission": "view" + } + ], + "org_access": "view", + "id": "8d2e1c7a-3b4f-4a2e-9c1d-2f3a4b5c6d7e", + "name": "Opportunities created by journeys every month", + "description": "Monthly count of opportunities grouped by source journey", + "visualisation_id": "timechart", + "visualisation_config": { + "query": { + "dataset": "entity_operations", + "measure": "count_operations", + "dimensions": [ + { + "time_with_granularity": "year-month" + } + ], + "filters": [ + { + "entity_schema": "opportunity" + } + ] + }, + "options": { + "type": "bar" + } + }, + "tags": ["revenue", "marketing"] } ``` diff --git a/packages/cli/docs/deduplication.md b/packages/cli/docs/deduplication.md index 1ded34d10..382742cee 100644 --- a/packages/cli/docs/deduplication.md +++ b/packages/cli/docs/deduplication.md @@ -38,6 +38,13 @@ epilot deduplication deduplicate - [`deduplicate`](#deduplicate) — Deduplicates Entities - [`deduplicateAsync`](#deduplicateasync) — Submits an async deduplication job. Returns a job ID immediately. Poll GET /v1/deduplicate/jobs/{jobId} for status. - [`getDeduplicationJob`](#getdeduplicationjob) — Returns the current status of an async deduplication job +- [`detectDuplicates`](#detectduplicates) — Detects potential duplicate entities for the given entity using the schema's prioritized uniqueness rules. Returns match +- [`dismissDuplicates`](#dismissduplicates) — Confirms entities as NOT duplicates: clears the internal duplicate-detection flags (_matching_entities) on each given en +- [`listUniquenessCriteria`](#listuniquenesscriteria) — Lists UniquenessCriteria for the requesting organization. Optionally filtered by schema. +- [`createUniquenessCriteria`](#createuniquenesscriteria) — Creates a new UniquenessCriteria record. +- [`getUniquenessCriteria`](#getuniquenesscriteria) — Fetch a single UniquenessCriteria record. +- [`updateUniquenessCriteria`](#updateuniquenesscriteria) — Replace the matchRules on an existing UniquenessCriteria record. +- [`deleteUniquenessCriteria`](#deleteuniquenesscriteria) — Delete a UniquenessCriteria record. ### `deduplicate` @@ -63,33 +70,35 @@ cat body.json | epilot deduplication deduplicate With JSONata filter: ```bash -epilot deduplication deduplicate --jsonata '$' +epilot deduplication deduplicate --jsonata 'deduplicatedEntities' ```
Sample Response ```json -[ - { - "_id": "string", - "_org": "string", - "_schema": "string", - "_created_at": "1970-01-01T00:00:00.000Z", - "_updated_at": "1970-01-01T00:00:00.000Z", - "_created_by": "string", - "created_by": "string", - "_tags": ["string"], - "_acl": {}, - "_owners": [ - { - "org_id": "string", - "user_id": "string" - } - ], - "type": "string" - } -] +{ + "deduplicatedEntities": [ + { + "_id": "string", + "_org": "string", + "_schema": "string", + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z", + "_created_by": "string", + "created_by": "string", + "_tags": ["string"], + "_acl": {}, + "_owners": [ + { + "org_id": "string", + "user_id": "string" + } + ], + "type": "string" + } + ] +} ```
@@ -192,3 +201,377 @@ epilot deduplication getDeduplicationJob -p jobId=123e4567-e89b-12d3-a456-426614
--- + +### `detectDuplicates` + +Detects potential duplicate entities for the given entity using the schema's prioritized uniqueness rules. Returns match + +`POST /v1/detect-duplicates` + +**Request Body** + +**Sample Call** + +```bash +epilot deduplication detectDuplicates \ + -d '{"schema":"string","entity":{}}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot deduplication detectDuplicates +``` + +With JSONata filter: + +```bash +epilot deduplication detectDuplicates --jsonata 'matches' +``` + +
+Sample Response + +```json +{ + "matches": [ + { + "entity": { + "_id": "string", + "_org": "string", + "_schema": "string", + "_created_at": "1970-01-01T00:00:00.000Z", + "_updated_at": "1970-01-01T00:00:00.000Z", + "_created_by": "string", + "created_by": "string", + "_tags": ["string"], + "_acl": {}, + "_owners": [ + { + "org_id": "string", + "user_id": "string" + } + ], + "type": "string" + }, + "confidence": 0, + "matched_attributes": ["string"] + } + ] +} +``` + +
+ +--- + +### `dismissDuplicates` + +Confirms entities as NOT duplicates: clears the internal duplicate-detection flags (_matching_entities) on each given en + +`POST /v1/duplicates/dismiss` + +**Request Body** + +**Sample Call** + +```bash +epilot deduplication dismissDuplicates \ + -d '{"schema":"string","entityIds":["string"]}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot deduplication dismissDuplicates +``` + +With JSONata filter: + +```bash +epilot deduplication dismissDuplicates --jsonata 'dismissed' +``` + +
+Sample Response + +```json +{ + "dismissed": ["string"] +} +``` + +
+ +--- + +### `listUniquenessCriteria` + +Lists UniquenessCriteria for the requesting organization. Optionally filtered by schema. + +`GET /v1/uniqueness-criteria` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `schema` | query | string | No | Filter results to a specific entity schema. | + +**Sample Call** + +```bash +epilot deduplication listUniquenessCriteria +``` + +With JSONata filter: + +```bash +epilot deduplication listUniquenessCriteria --jsonata 'items[0]' +``` + +
+Sample Response + +```json +{ + "items": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "orgId": "string", + "schema": "string", + "matchRules": [ + { + "name": "string", + "attributes": [ + { + "attribute": "string" + } + ], + "confidence": 0 + } + ], + "createdAt": "1970-01-01T00:00:00.000Z", + "updatedAt": "1970-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `createUniquenessCriteria` + +Creates a new UniquenessCriteria record. + +`POST /v1/uniqueness-criteria` + +**Request Body** + +**Sample Call** + +```bash +epilot deduplication createUniquenessCriteria \ + -d '{"schema":"string","matchRules":[{"name":"string","attributes":[{"attribute":"string"}],"confidence":0}]}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot deduplication createUniquenessCriteria +``` + +With JSONata filter: + +```bash +epilot deduplication createUniquenessCriteria --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "orgId": "string", + "schema": "string", + "matchRules": [ + { + "name": "string", + "attributes": [ + { + "attribute": "string" + } + ], + "confidence": 0 + } + ], + "createdAt": "1970-01-01T00:00:00.000Z", + "updatedAt": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `getUniquenessCriteria` + +Fetch a single UniquenessCriteria record. + +`GET /v1/uniqueness-criteria/{schema}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `schema` | path | string | Yes | Entity schema slug. | + +**Sample Call** + +```bash +epilot deduplication getUniquenessCriteria \ + -p schema=example +``` + +Using positional args for path parameters: + +```bash +epilot deduplication getUniquenessCriteria example +``` + +With JSONata filter: + +```bash +epilot deduplication getUniquenessCriteria -p schema=example --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "orgId": "string", + "schema": "string", + "matchRules": [ + { + "name": "string", + "attributes": [ + { + "attribute": "string" + } + ], + "confidence": 0 + } + ], + "createdAt": "1970-01-01T00:00:00.000Z", + "updatedAt": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `updateUniquenessCriteria` + +Replace the matchRules on an existing UniquenessCriteria record. + +`PUT /v1/uniqueness-criteria/{schema}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `schema` | path | string | Yes | Entity schema slug. | + +**Request Body** + +**Sample Call** + +```bash +epilot deduplication updateUniquenessCriteria \ + -p schema=example \ + -d '{"matchRules":[{"name":"string","attributes":[{"attribute":"string"}],"confidence":0}]}' +``` + +Using positional args for path parameters: + +```bash +epilot deduplication updateUniquenessCriteria example +``` + +Using stdin pipe: + +```bash +cat body.json | epilot deduplication updateUniquenessCriteria -p schema=example +``` + +With JSONata filter: + +```bash +epilot deduplication updateUniquenessCriteria -p schema=example --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "orgId": "string", + "schema": "string", + "matchRules": [ + { + "name": "string", + "attributes": [ + { + "attribute": "string" + } + ], + "confidence": 0 + } + ], + "createdAt": "1970-01-01T00:00:00.000Z", + "updatedAt": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteUniquenessCriteria` + +Delete a UniquenessCriteria record. + +`DELETE /v1/uniqueness-criteria/{schema}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `schema` | path | string | Yes | Entity schema slug. | + +**Sample Call** + +```bash +epilot deduplication deleteUniquenessCriteria \ + -p schema=example +``` + +Using positional args for path parameters: + +```bash +epilot deduplication deleteUniquenessCriteria example +``` + +With JSONata filter: + +```bash +epilot deduplication deleteUniquenessCriteria -p schema=example --jsonata '$' +``` + +--- diff --git a/packages/cli/docs/design.md b/packages/cli/docs/design.md index ad268466f..3fb7c85bf 100644 --- a/packages/cli/docs/design.md +++ b/packages/cli/docs/design.md @@ -39,6 +39,7 @@ epilot design getAllDesigns - [`getDesign`](#getdesign) — Search for a especific design owned by user organization - [`updateDesign`](#updatedesign) — Update a especific design owned by user organization - [`deleteDesign`](#deletedesign) — Search and delete for a especific design owned by user organization +- [`duplicateDesign`](#duplicatedesign) — Duplicate an existing design owned by the user organization. The copy carries over the style, custom CSS and name (marke - [`getThemeFromDesign`](#getthemefromdesign) — Search for a especific design owned by user organization and parse them to a new or old theme - [`getFiles`](#getfiles) — List all files for the user organization bucket - [`uploadFile`](#uploadfile) — Upload a new file for the user organization bucket @@ -613,6 +614,138 @@ epilot design deleteDesign -p designId=4a062990-a6a3-11eb-9828-4f3da7d4935a --js --- +### `duplicateDesign` + +Duplicate an existing design owned by the user organization. The copy carries over the style, custom CSS and name (marke + +`POST /v1/designs/{designId}/duplicate` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `designId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot design duplicateDesign \ + -p designId=4a062990-a6a3-11eb-9828-4f3da7d4935a +``` + +Using positional args for path parameters: + +```bash +epilot design duplicateDesign 4a062990-a6a3-11eb-9828-4f3da7d4935a +``` + +With JSONata filter: + +```bash +epilot design duplicateDesign -p designId=4a062990-a6a3-11eb-9828-4f3da7d4935a --jsonata 'design' +``` + +
+Sample Response + +```json +{ + "design": { + "id": "string", + "created_at": "2021-01-30T08:30:00Z", + "created_by": "string", + "edited": true, + "last_modified_at": "string", + "brand_id": "string", + "brand_name": "string", + "user": { + "emailaddress": "string", + "fullname": "string", + "name": "string", + "userid": "string" + }, + "style_name": "string", + "style": { + "logo": {}, + "palette": {}, + "typography": {}, + "shape": {}, + "consumer": {} + }, + "is_default": true, + "_manifest": ["string"], + "custom_theme": "string", + "use_custom_theme": true, + "design_tokens": { + "coupon": "string", + "cashback": "string", + "custom_css": "string", + "accent_color": "string", + "outline_color": "string", + "divider_color": "string", + "link_color": "string", + "link_hover_color": "string", + "font_size_scale": "xs", + "topbar_height": 0, + "topbar_logo_alignment": "flex-start", + "logo_size": 0, + "input_background": "string", + "input_border_color": "string", + "input_text_color": "string", + "input_label_color": "string", + "input_border_radius": 0, + "input_height": 0, + "input_variant": "outlined", + "button_primary_bg": "string", + "button_primary_text": "string", + "button_primary_hover_bg": "string", + "button_primary_hover_text": "string", + "button_outlined_border": "string", + "button_outlined_text": "string", + "button_outlined_hover_bg": "string", + "button_outlined_hover_text": "string", + "button_ghost_bg": "string", + "button_ghost_text": "string", + "button_ghost_hover_bg": "string", + "button_ghost_hover_text": "string", + "button_border_radius": 0, + "button_height": 0, + "card_background": "string", + "card_border_color": "string", + "card_variant": "shadow", + "summary_card_background": "string", + "toggle_selected_bg": "string", + "toggle_selected_text": "string", + "toggle_hover_bg": "string", + "toggle_hover_text": "string", + "toggle_border_color": "string", + "dropdown_hover_bg": "string", + "dropdown_hover_text": "string", + "dropdown_selected_bg": "string", + "dropdown_selected_text": "string", + "switch_unchecked_color": "string", + "switch_unchecked_bg": "string", + "switch_border_radius": 0, + "checkbox_unchecked_color": "string", + "checkbox_label_color": "string", + "radio_unchecked_color": "string", + "radio_label_color": "string", + "datepicker_selected_bg": "string", + "datepicker_selected_color": "string", + "datepicker_border_radius": 0, + "chip_background": "string", + "chip_hover_background": "string", + "chip_text_color": "string", + "chip_hover_text_color": "string" + } + } +} +``` + +
+ +--- + ### `getThemeFromDesign` Search for a especific design owned by user organization and parse them to a new or old theme diff --git a/packages/cli/docs/email-settings.md b/packages/cli/docs/email-settings.md index bccbec2a5..6577ed5c2 100644 --- a/packages/cli/docs/email-settings.md +++ b/packages/cli/docs/email-settings.md @@ -57,6 +57,9 @@ epilot email-settings provisionEpilotEmailAddress **O365 Outlook Connection** - [`connectOutlook`](#connectoutlook) — Returns the Microsoft authorization URL for Outlook OAuth. - [`getOutlookConnectionStatus`](#getoutlookconnectionstatus) — Returns all Microsoft 365 / Outlook connections for the organization. +- [`getCalendarAdminConsentStatus`](#getcalendaradminconsentstatus) — Reports whether the caller's organization can connect personal Outlook +- [`getMyCalendarConnection`](#getmycalendarconnection) — Returns the calling user's personal Outlook calendar connection, +- [`disconnectMyCalendar`](#disconnectmycalendar) — Removes the calling user's personal calendar connection. - [`disconnectOutlook`](#disconnectoutlook) — Removes the Microsoft 365 / Outlook connection for a specific tenant. - [`connectOutlookMailbox`](#connectoutlookmailbox) — Connects an Outlook mailbox: - [`disconnectOutlookMailbox`](#disconnectoutlookmailbox) — Disconnects a single Outlook mailbox by email address. @@ -71,6 +74,17 @@ epilot email-settings provisionEpilotEmailAddress - [`disconnectMsTeams`](#disconnectmsteams) — Disconnects Microsoft Teams channel for the organization. - [`getMsTeamsStatus`](#getmsteamsstatus) — Returns the connection status of the Microsoft Teams channel for the organization. +**Custom SMTP** +- [`listSmtpConnections`](#listsmtpconnections) — Returns all custom SMTP connections configured for the organization. +- [`createSmtpConnection`](#createsmtpconnection) — Creates a new custom SMTP connection. Runs a live verify against the SMTP server +- [`getSmtpConnection`](#getsmtpconnection) — Returns a single custom SMTP connection by id. The password is never returned. +- [`updateSmtpConnection`](#updatesmtpconnection) — Partial update; omitted fields keep their existing values. The merged +- [`deleteSmtpConnection`](#deletesmtpconnection) — Deletes a custom SMTP connection. Messages already sent are unaffected. +- [`testSmtpConnection`](#testsmtpconnection) — Re-runs a live SMTP verify against the saved configuration (EHLO + AUTH + NOOP + QUIT) +- [`listSmtpSenders`](#listsmtpsenders) — Returns every address registered to send through a custom SMTP connection. +- [`connectSmtpSender`](#connectsmtpsender) — Registers an address as a sender on a custom SMTP connection: +- [`disconnectSmtpSender`](#disconnectsmtpsender) — Removes a sender address: deletes the email address and its binding to the SMTP + **Settings** - [`getSettings`](#getsettings) — Retrieves settings of a specific type for the organization. - [`addSetting`](#addsetting) — Creates a new setting of the specified type. @@ -797,7 +811,7 @@ Returns the Microsoft authorization URL for Outlook OAuth. ```bash epilot email-settings connectOutlook \ - -d '{"mail":true,"calendar":true}' + -d '{"mail":true,"calendar":true,"return_to":"string"}' ``` Using stdin pipe: @@ -873,6 +887,99 @@ epilot email-settings getOutlookConnectionStatus --jsonata 'connections' --- +### `getCalendarAdminConsentStatus` + +Reports whether the caller's organization can connect personal Outlook + +`GET /v2/outlook/calendar/admin-consent-status` + +**Sample Call** + +```bash +epilot email-settings getCalendarAdminConsentStatus +``` + +With JSONata filter: + +```bash +epilot email-settings getCalendarAdminConsentStatus --jsonata 'admin_consented' +``` + +
+Sample Response + +```json +{ + "admin_consented": true, + "admin_consent_url": "https://example.com/path" +} +``` + +
+ +--- + +### `getMyCalendarConnection` + +Returns the calling user's personal Outlook calendar connection, + +`GET /v2/outlook/calendar/me` + +**Sample Call** + +```bash +epilot email-settings getMyCalendarConnection +``` + +With JSONata filter: + +```bash +epilot email-settings getMyCalendarConnection --jsonata 'connection' +``` + +
+Sample Response + +```json +{ + "connection": { + "user_id": "string", + "tenant_id": "string", + "status": "connected", + "scopes": ["string"], + "connected_by_display_name": "string", + "connected_by_email": "user@example.com", + "connected_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "expires_at": "1970-01-01T00:00:00.000Z" + } +} +``` + +
+ +--- + +### `disconnectMyCalendar` + +Removes the calling user's personal calendar connection. + +`DELETE /v2/outlook/calendar/me` + +**Sample Call** + +```bash +epilot email-settings disconnectMyCalendar +``` + +With JSONata filter: + +```bash +epilot email-settings disconnectMyCalendar --jsonata '$' +``` + +--- + ### `disconnectOutlook` Removes the Microsoft 365 / Outlook connection for a specific tenant. @@ -1021,9 +1128,23 @@ Connects an Outlook mailbox: **Sample Call** +```bash +epilot email-settings connectOutlookMailbox +``` + +With request body: + ```bash epilot email-settings connectOutlookMailbox \ - -d '{"email":"user@example.com","shared_inbox_id":"default","mailboxSyncTimeframe":"5m"}' + -d '{ + "email": "user@example.com", + "shared_inbox_id": "default", + "name": "string", + "user_ids": ["string"], + "group_ids": ["string"], + "default_signature_id": "string", + "mailboxSyncTimeframe": "5m" +}' ``` Using stdin pipe: @@ -1298,6 +1419,506 @@ epilot email-settings getConnectedOutlookEmails --jsonata 'outlook_emails' --- +### `listSmtpConnections` + +Returns all custom SMTP connections configured for the organization. + +`GET /v2/smtp/connections` + +**Sample Call** + +```bash +epilot email-settings listSmtpConnections +``` + +With JSONata filter: + +```bash +epilot email-settings listSmtpConnections --jsonata 'connections' +``` + +
+Sample Response + +```json +{ + "connections": [ + { + "connection_id": "string", + "smtp_host": "string", + "smtp_port": "string", + "smtp_secure": "tls", + "smtp_username": "string", + "smtp_password": "string", + "connected_by_display_name": "string", + "connected_by_email": "user@example.com", + "connected_by_user_id": "string", + "connected_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "last_tested_at": "1970-01-01T00:00:00.000Z", + "last_test_status": "ok", + "last_test_error": "string" + } + ], + "has_connections": true +} +``` + +
+ +--- + +### `createSmtpConnection` + +Creates a new custom SMTP connection. Runs a live verify against the SMTP server + +`POST /v2/smtp/connections` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `skip_test` | query | boolean | No | Save without running the live verify. Use when the server is not reachable from +epilot yet, or when a referenced `{{ env.* }}` secret has not been set. The +connection is stored untested — `last_test_s | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot email-settings createSmtpConnection +``` + +With request body: + +```bash +epilot email-settings createSmtpConnection \ + -d '{ + "smtp_host": "string", + "smtp_port": "string", + "smtp_secure": "tls", + "smtp_username": "string", + "smtp_password": "{{ env.smtp_password }}" +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot email-settings createSmtpConnection +``` + +With JSONata filter: + +```bash +epilot email-settings createSmtpConnection --jsonata 'connection_id' +``` + +
+Sample Response + +```json +{ + "connection_id": "string", + "smtp_host": "string", + "smtp_port": "string", + "smtp_secure": "tls", + "smtp_username": "string", + "smtp_password": "string", + "connected_by_display_name": "string", + "connected_by_email": "user@example.com", + "connected_by_user_id": "string", + "connected_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "last_tested_at": "1970-01-01T00:00:00.000Z", + "last_test_status": "ok", + "last_test_error": "string" +} +``` + +
+ +--- + +### `getSmtpConnection` + +Returns a single custom SMTP connection by id. The password is never returned. + +`GET /v2/smtp/connections/{connectionId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `connectionId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot email-settings getSmtpConnection \ + -p connectionId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot email-settings getSmtpConnection 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot email-settings getSmtpConnection -p connectionId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'connection_id' +``` + +
+Sample Response + +```json +{ + "connection_id": "string", + "smtp_host": "string", + "smtp_port": "string", + "smtp_secure": "tls", + "smtp_username": "string", + "smtp_password": "string", + "connected_by_display_name": "string", + "connected_by_email": "user@example.com", + "connected_by_user_id": "string", + "connected_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "last_tested_at": "1970-01-01T00:00:00.000Z", + "last_test_status": "ok", + "last_test_error": "string" +} +``` + +
+ +--- + +### `updateSmtpConnection` + +Partial update; omitted fields keep their existing values. The merged + +`PUT /v2/smtp/connections/{connectionId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `connectionId` | path | string | Yes | | +| `skip_test` | query | boolean | No | Save without running the live verify. Use when the server is not reachable from +epilot yet, or when a referenced `{{ env.* }}` secret has not been set. The +connection is stored untested — `last_test_s | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot email-settings updateSmtpConnection \ + -p connectionId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"smtp_host":"string","smtp_port":"string","smtp_secure":"tls","smtp_username":"string","smtp_password":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot email-settings updateSmtpConnection 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot email-settings updateSmtpConnection -p connectionId=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot email-settings updateSmtpConnection -p connectionId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'connection_id' +``` + +
+Sample Response + +```json +{ + "connection_id": "string", + "smtp_host": "string", + "smtp_port": "string", + "smtp_secure": "tls", + "smtp_username": "string", + "smtp_password": "string", + "connected_by_display_name": "string", + "connected_by_email": "user@example.com", + "connected_by_user_id": "string", + "connected_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "last_tested_at": "1970-01-01T00:00:00.000Z", + "last_test_status": "ok", + "last_test_error": "string" +} +``` + +
+ +--- + +### `deleteSmtpConnection` + +Deletes a custom SMTP connection. Messages already sent are unaffected. + +`DELETE /v2/smtp/connections/{connectionId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `connectionId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot email-settings deleteSmtpConnection \ + -p connectionId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot email-settings deleteSmtpConnection 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot email-settings deleteSmtpConnection -p connectionId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'success' +``` + +
+Sample Response + +```json +{ + "success": true, + "connection_id": "string" +} +``` + +
+ +--- + +### `testSmtpConnection` + +Re-runs a live SMTP verify against the saved configuration (EHLO + AUTH + NOOP + QUIT) + +`POST /v2/smtp/connections/{connectionId}/test` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `connectionId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot email-settings testSmtpConnection \ + -p connectionId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot email-settings testSmtpConnection 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot email-settings testSmtpConnection -p connectionId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'status' +``` + +
+Sample Response + +```json +{ + "status": "ok", + "error": "string", + "tested_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `listSmtpSenders` + +Returns every address registered to send through a custom SMTP connection. + +`GET /v2/smtp/senders` + +**Sample Call** + +```bash +epilot email-settings listSmtpSenders +``` + +With JSONata filter: + +```bash +epilot email-settings listSmtpSenders --jsonata 'senders' +``` + +
+Sample Response + +```json +{ + "senders": [ + { + "email": "user@example.com", + "connection_id": "string", + "reply_to_email": "string", + "connected_at": "1970-01-01T00:00:00.000Z", + "connected_by_user_id": "string" + } + ] +} +``` + +
+ +--- + +### `connectSmtpSender` + +Registers an address as a sender on a custom SMTP connection: + +`POST /v2/smtp/senders` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot email-settings connectSmtpSender +``` + +With request body: + +```bash +epilot email-settings connectSmtpSender \ + -d '{ + "email": "user@example.com", + "connection_id": "string", + "name": "string", + "reply_to_email": "string", + "shared_inbox_id": "default", + "user_ids": ["string"], + "group_ids": ["string"], + "default_signature_id": "string" +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot email-settings connectSmtpSender +``` + +With JSONata filter: + +```bash +epilot email-settings connectSmtpSender --jsonata 'email_address' +``` + +
+Sample Response + +```json +{ + "email_address": { + "id": "a10bd0ff-4391-4cfc-88ee-b19d718a9bf7", + "created_at": "2024-01-15T10:30:00Z", + "updated_at": "2024-01-20T14:45:00Z", + "created_by": "user-123", + "updated_by": "user-456", + "address": "sales@yourcompany.com", + "name": "Sales Team", + "user_ids": ["user-123", "user-456"], + "group_ids": ["group-789"], + "default_signature_id": "sig-abc", + "shared_inbox_id": "inbox-xyz", + "is_active": true, + "is_primary": false, + "is_epilot_email_address": false + }, + "sender": { + "email": "user@example.com", + "connection_id": "string", + "reply_to_email": "string", + "connected_at": "1970-01-01T00:00:00.000Z", + "connected_by_user_id": "string" + } +} +``` + +
+ +--- + +### `disconnectSmtpSender` + +Removes a sender address: deletes the email address and its binding to the SMTP + +`DELETE /v2/smtp/senders/{email}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `email` | path | string (email) | Yes | | + +**Sample Call** + +```bash +epilot email-settings disconnectSmtpSender \ + -p email=user@example.com +``` + +Using positional args for path parameters: + +```bash +epilot email-settings disconnectSmtpSender user@example.com +``` + +With JSONata filter: + +```bash +epilot email-settings disconnectSmtpSender -p email=user@example.com --jsonata 'email' +``` + +
+Sample Response + +```json +{ + "success": true, + "email": "user@example.com" +} +``` + +
+ +--- + ### `outlookOAuthCallback` Exchanges authorization code for tokens and stores them. @@ -1318,6 +1939,7 @@ Exchanges authorization code for tokens and stores them. | `error_uri` | query | string | No | | | `admin_consent` | query | string | No | | | `tenant` | query | string | No | | +| `clientdata` | query | string | No | | **Sample Call** diff --git a/packages/cli/docs/entity-mapping.md b/packages/cli/docs/entity-mapping.md index c5fd7a616..ce6dd9dc4 100644 --- a/packages/cli/docs/entity-mapping.md +++ b/packages/cli/docs/entity-mapping.md @@ -88,13 +88,15 @@ epilot entity-mapping storeConfig \ "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -145,13 +147,15 @@ epilot entity-mapping storeConfig --jsonata '$' "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -225,13 +229,15 @@ epilot entity-mapping getConfig -p id=70542580-2b38-4bfc-af8d-bb90102f9f47 --jso "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -305,13 +311,15 @@ epilot entity-mapping deleteConfig -p id=70542580-2b38-4bfc-af8d-bb90102f9f47 -- "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -432,13 +440,15 @@ epilot entity-mapping storeNewVersion \ "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -495,13 +505,15 @@ epilot entity-mapping storeNewVersion -p id=70542580-2b38-4bfc-af8d-bb90102f9f47 "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -577,13 +589,15 @@ epilot entity-mapping getConfigVersion -p id=70542580-2b38-4bfc-af8d-bb90102f9f4 "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -636,6 +650,10 @@ epilot entity-mapping executeMapping \ "entity_id": "string", "entity_schema": "submission" }, + "loop_ref": { + "entity_id": "string", + "entity_schema": "submission" + }, "targets": [ { "id": "string", @@ -643,66 +661,15 @@ epilot entity-mapping executeMapping \ "allow_failure": true, "target_schema": "string", "target_unique": ["string"], - "loop_config": { - "source_path": "string", - "length": "the length of the array" - }, + "use_uniqueness_criteria": false, + "loop_config": {}, "conditionMode": "oneOf", - "conditions": [ - { - "_exists": { - "source": "string", - "value": "string" - }, - "_equals": { - "source": "string", - "value": "string" - }, - "_not_exists": { - "source": "string", - "value": "string" - }, - "_any_of": { - "source": "string", - "value": "string" - } - } - ], - "mapping_attributes": [ - { - "target": "_tags", - "operation": { - "_append": ["new", "tags"], - "_uniq": true - } - }, - { - "mode": "copy_if_exists", - "target": "string", - "value": {} - } - ], - "relation_attributes": [ - { - "target": "string", - "target_tags": ["string"], - "target_tags_include_source": false, - "override_with_source_filter": false, - "source_filter": { - "limit": 0, - "schema": "string", - "attribute": "string", - "relation_tag": "string", - "tag": "string", - "self": false - }, - "related_to": {}, - "mode": "append", - "origin": "system_recommendation" - } - ], + "conditions": [], + "mapping_attributes": [], + "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ] }' @@ -803,13 +770,15 @@ epilot entity-mapping searchConfigs --jsonata '$' "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -904,6 +873,7 @@ epilot entity-mapping executeRelations \ "target": "string", "target_tags": ["string"], "target_tags_include_source": false, + "target_entity_tags": ["string"], "override_with_source_filter": false, "source_filter": { "limit": 0, @@ -1014,13 +984,15 @@ epilot entity-mapping getMappingConfig -p id=70542580-2b38-4bfc-af8d-bb90102f9f4 "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -1085,13 +1057,15 @@ epilot entity-mapping putMappingConfig \ "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", @@ -1148,13 +1122,15 @@ epilot entity-mapping putMappingConfig -p id=70542580-2b38-4bfc-af8d-bb90102f9f4 "allow_failure": true, "target_schema": "string", "target_unique": ["string"], + "use_uniqueness_criteria": false, "loop_config": {}, "conditionMode": "oneOf", "conditions": [], "mapping_attributes": [], "relation_attributes": [], "linkback_relation_attribute": "mapped_entities", - "linkback_relation_tags": ["string"] + "linkback_relation_tags": ["string"], + "graph_context": [] } ], "created_at": "1970-01-01T00:00:00.000Z", diff --git a/packages/cli/docs/entity.md b/packages/cli/docs/entity.md index 1cabbb910..eded54738 100644 --- a/packages/cli/docs/entity.md +++ b/packages/cli/docs/entity.md @@ -38,14 +38,12 @@ epilot entity listSchemas **Schemas** - [`listSchemas`](#listschemas) — Get the latest versions of all schemas - [`listSchemasV2`](#listschemasv2) — Get the latest versions of all schemas. -- [`getSchema`](#getschema) — By default gets the current version of the Schema (frozen version if frozen, otherwise latest). +- [`getSchema`](#getschema) — Gets the latest version of the Schema. - [`putSchema`](#putschema) — Create or update a schema with a new version. - [`deleteSchema`](#deleteschema) — Delete a schema, or a specific version of a schema - [`getJsonSchema`](#getjsonschema) — Get formal JSON schema definition draft 2020-12 for the given epilot schema - [`getSchemaExample`](#getschemaexample) — Get a full example entity for the given schema - [`getSchemaVersions`](#getschemaversions) — Get all versions of this schema ordered by the latest versions including drafts. -- [`freezeSchema`](#freezeschema) — Freeze a schema at its current version, or at a specific version. -- [`unfreezeSchema`](#unfreezeschema) — Unfreeze a schema. Promotes the latest version to the current version for all users. - [`listAvailableCapabilities`](#listavailablecapabilities) — List available capabilities for schema - [`listSchemaBlueprints`](#listschemablueprints) — List canonical versions of all available schemas - [`listTaxonomyClassificationsForSchema`](#listtaxonomyclassificationsforschema) — List taxonomy classifications for a given schema @@ -106,6 +104,7 @@ epilot entity listSchemas **Import-Export** - [`exportEntities`](#exportentities) — Export entity data in a CSV-format. The export will export data as close as possible to what is visible on Entity UI tab - [`importEntities`](#importentities) — This endpoint enables the import of entities into the platform. +- [`abortEntityImport`](#abortentityimport) — Asks a running import to stop. Rows already imported are **kept** - this is a stop, not a **Saved Views** - [`listSavedViews`](#listsavedviews) — Get the Saved Views based on the schema @@ -147,7 +146,8 @@ Get the latest versions of all schemas | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `unpublished` | query | boolean | No | Return unpublished draft schemas | -| `latest` | query | boolean | No | When true, return the latest version instead of the frozen version for frozen schemas. | +| `latest` | query | boolean | No | No longer has any effect: schema freezing is retired and the latest version is always returned. + | | `exclude` | query | string[] | No | List of schema slugs to exclude from the results. Accepts a comma-separated list of slugs to exclude from the results. | | `include` | query | string[] | No | List of schema slugs to include in the results. When provided, only these schemas are returned. Accepts a comma-separated list of slugs. | @@ -201,7 +201,8 @@ epilot entity listSchemas --jsonata 'results[0]' "_purpose": ["string"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "explicit_search_mappings": {}, - "group_headlines": [] + "group_headlines": [], + "conditions": [] } ] } @@ -223,7 +224,8 @@ Get the latest versions of all schemas. | ---- | -- | ---- | -------- | ----------- | | `full` | query | boolean | No | Return full schemas including all attributes and capabilities | | `unpublished` | query | boolean | No | Return unpublished draft schemas | -| `latest` | query | boolean | No | When true, return the latest version instead of the frozen version for frozen schemas. | +| `latest` | query | boolean | No | No longer has any effect: schema freezing is retired and the latest version is always returned. + | | `exclude` | query | string[] | No | List of schema slugs to exclude from the results. Accepts a comma-separated list of slugs to exclude from the results. | | `include` | query | string[] | No | List of schema slugs to include in the results. When provided, only these schemas are returned. Accepts a comma-separated list of slugs. | @@ -277,7 +279,8 @@ epilot entity listSchemasV2 --jsonata 'results[0]' "_purpose": ["string"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "explicit_search_mappings": {}, - "group_headlines": [] + "group_headlines": [], + "conditions": [] } ] } @@ -289,7 +292,7 @@ epilot entity listSchemasV2 --jsonata 'results[0]' ### `getSchema` -By default gets the current version of the Schema (frozen version if frozen, otherwise latest). +Gets the latest version of the Schema. `GET /v1/entity/schemas/{slug}` @@ -299,7 +302,8 @@ By default gets the current version of the Schema (frozen version if frozen, oth | ---- | -- | ---- | -------- | ----------- | | `slug` | path | string | Yes | Entity Type | | `id` | query | string (uuid) | No | | -| `latest` | query | boolean | No | When true, return the latest version instead of the frozen version for frozen schemas. | +| `latest` | query | boolean | No | No longer has any effect: schema freezing is retired and the latest version is always returned. + | **Sample Call** @@ -379,6 +383,20 @@ epilot entity getSchema -p slug=contact --jsonata '$' }, "sharing": { "show_sharing_button": true + }, + "grid_layout": { + "id": "string", + "columns": 0, + "cells": [0] + }, + "widget_order": ["string"], + "widget_visibility": { + "next_best_action": true, + "address_map": false + }, + "widget_widths": { + "address_map": "full_width", + "recent_communications": "one_third_width" } }, "capabilities": [ @@ -387,9 +405,11 @@ epilot entity getSchema -p slug=contact --jsonata '$' "name": "customer_messaging", "title": "Messaging", "attributes": [], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": {}, "ui_config": {}, "ui_hooks": [], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -452,6 +472,14 @@ epilot entity getSchema -p slug=contact --jsonata '$' "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] } + ], + "conditions": [ + { + "id": "d5839b94-ba20-4225-a78e-76951d352bd6", + "name": "delivery_area", + "label": "Delivery Area", + "type": "string" + } ] } ``` @@ -531,6 +559,20 @@ epilot entity putSchema \ }, "sharing": { "show_sharing_button": true + }, + "grid_layout": { + "id": "string", + "columns": 0, + "cells": [0] + }, + "widget_order": ["string"], + "widget_visibility": { + "next_best_action": true, + "address_map": false + }, + "widget_widths": { + "address_map": "full_width", + "recent_communications": "one_third_width" } }, "capabilities": [ @@ -539,9 +581,11 @@ epilot entity putSchema \ "name": "customer_messaging", "title": "Messaging", "attributes": [], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": {}, "ui_config": {}, "ui_hooks": [], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -604,6 +648,14 @@ epilot entity putSchema \ "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] } + ], + "conditions": [ + { + "id": "d5839b94-ba20-4225-a78e-76951d352bd6", + "name": "delivery_area", + "label": "Delivery Area", + "type": "string" + } ] }' ``` @@ -685,6 +737,20 @@ epilot entity putSchema -p slug=contact --jsonata '$' }, "sharing": { "show_sharing_button": true + }, + "grid_layout": { + "id": "string", + "columns": 0, + "cells": [0] + }, + "widget_order": ["string"], + "widget_visibility": { + "next_best_action": true, + "address_map": false + }, + "widget_widths": { + "address_map": "full_width", + "recent_communications": "one_third_width" } }, "capabilities": [ @@ -693,9 +759,11 @@ epilot entity putSchema -p slug=contact --jsonata '$' "name": "customer_messaging", "title": "Messaging", "attributes": [], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": {}, "ui_config": {}, "ui_hooks": [], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -758,6 +826,14 @@ epilot entity putSchema -p slug=contact --jsonata '$' "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] } + ], + "conditions": [ + { + "id": "d5839b94-ba20-4225-a78e-76951d352bd6", + "name": "delivery_area", + "label": "Delivery Area", + "type": "string" + } ] } ``` @@ -1062,7 +1138,8 @@ epilot entity getSchemaVersions -p slug=contact --jsonata 'versions' "_purpose": ["string"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "explicit_search_mappings": {}, - "group_headlines": [] + "group_headlines": [], + "conditions": [] } ], "drafts": [ @@ -1098,7 +1175,8 @@ epilot entity getSchemaVersions -p slug=contact --jsonata 'versions' "_purpose": ["string"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "explicit_search_mappings": {}, - "group_headlines": [] + "group_headlines": [], + "conditions": [] } ], "versions_more": true, @@ -1111,357 +1189,6 @@ epilot entity getSchemaVersions -p slug=contact --jsonata 'versions' --- -### `freezeSchema` - -Freeze a schema at its current version, or at a specific version. - -`POST /v1/entity/schemas/{slug}/freeze` - -**Parameters** - -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `slug` | path | string | Yes | Entity Type | - -**Request Body** - -**Sample Call** - -```bash -epilot entity freezeSchema \ - -p slug=contact \ - -d '{"version_id":"3fa85f64-5717-4562-b3fc-2c963f66afa6"}' -``` - -Using positional args for path parameters: - -```bash -epilot entity freezeSchema contact -``` - -Using stdin pipe: - -```bash -cat body.json | epilot entity freezeSchema -p slug=contact -``` - -With JSONata filter: - -```bash -epilot entity freezeSchema -p slug=contact --jsonata '$' -``` - -
-Sample Response - -```json -{ - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "created_at": "string", - "updated_at": "string", - "comment": "string", - "source": { - "id": "string", - "type": "string" - }, - "frozen": true, - "latest": true, - "_summary": true, - "slug": "contact", - "version": 1, - "blueprint": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "feature_flag": "FF_MY_FEATURE_FLAG", - "enable_setting": ["360_features"], - "name": "Contact", - "plural": "Contacts", - "description": "Example description", - "docs_url": "https://docs.epilot.io/docs/pricing/entities", - "category": "customer_relations", - "published": false, - "draft": false, - "icon": "person", - "title_template": "{{first_name}} {{last_name}}", - "ui_config": { - "table_view": { - "view_type": "default", - "row_actions": ["string"], - "bulk_actions": ["string"], - "navbar_actions": [], - "enable_thumbnails": false - }, - "create_view": { - "view_type": "default", - "search_params": {} - }, - "edit_view": { - "view_type": "default", - "search_params": {}, - "summary_attributes": ["email"] - }, - "single_view": { - "view_type": "default", - "search_params": {}, - "summary_attributes": ["email"] - }, - "list_item": { - "summary_attributes": [], - "quick_actions": [], - "ui_config": {} - }, - "sharing": { - "show_sharing_button": true - } - }, - "capabilities": [ - { - "id": "d5839b94-ba20-4225-a78e-76951d352bd6", - "name": "customer_messaging", - "title": "Messaging", - "attributes": [], - "_purpose": ["taxonomy-slug:classification-slug"], - "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], - "app_id": "123e4567-e89b-12d3-a456-426614174000", - "ui_config": {}, - "ui_hooks": [], - "feature_flag": "FF_MY_FEATURE_FLAG", - "settings_flag": [], - "schemas": [] - } - ], - "group_settings": [ - { - "id": "e18a532b-ae79-4d86-a6a5-e5dbfb579d14", - "label": "Contact Details", - "expanded": true, - "order": 1 - }, - { - "id": "e9a1ae28-27ba-4fa0-a79c-e279cc5c4a6e", - "label": "Address Details", - "expanded": false, - "order": 2, - "info_tooltip_title": {} - } - ], - "layout_settings": { - "grid_gap": "string", - "grid_template_columns": "string" - }, - "dialog_config": {}, - "attributes": [ - { - "name": "email", - "type": "email", - "label": "Email", - "required": true - }, - { - "name": "first_name", - "type": "string", - "label": "First Name" - } - ], - "_purpose": ["string"], - "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], - "explicit_search_mappings": { - "image": { - "type": "keyword", - "index": false - } - }, - "group_headlines": [ - { - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "name": "string", - "label": "string", - "layout": "string", - "group": "string", - "order": 0, - "type": "headline", - "enable_divider": false, - "divider": "top_divider", - "_purpose": ["taxonomy-slug:classification-slug"], - "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] - } - ] -} -``` - -
- ---- - -### `unfreezeSchema` - -Unfreeze a schema. Promotes the latest version to the current version for all users. - -`POST /v1/entity/schemas/{slug}/unfreeze` - -**Parameters** - -| Name | In | Type | Required | Description | -| ---- | -- | ---- | -------- | ----------- | -| `slug` | path | string | Yes | Entity Type | - -**Sample Call** - -```bash -epilot entity unfreezeSchema \ - -p slug=contact -``` - -Using positional args for path parameters: - -```bash -epilot entity unfreezeSchema contact -``` - -With JSONata filter: - -```bash -epilot entity unfreezeSchema -p slug=contact --jsonata '$' -``` - -
-Sample Response - -```json -{ - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "created_at": "string", - "updated_at": "string", - "comment": "string", - "source": { - "id": "string", - "type": "string" - }, - "frozen": true, - "latest": true, - "_summary": true, - "slug": "contact", - "version": 1, - "blueprint": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "feature_flag": "FF_MY_FEATURE_FLAG", - "enable_setting": ["360_features"], - "name": "Contact", - "plural": "Contacts", - "description": "Example description", - "docs_url": "https://docs.epilot.io/docs/pricing/entities", - "category": "customer_relations", - "published": false, - "draft": false, - "icon": "person", - "title_template": "{{first_name}} {{last_name}}", - "ui_config": { - "table_view": { - "view_type": "default", - "row_actions": ["string"], - "bulk_actions": ["string"], - "navbar_actions": [], - "enable_thumbnails": false - }, - "create_view": { - "view_type": "default", - "search_params": {} - }, - "edit_view": { - "view_type": "default", - "search_params": {}, - "summary_attributes": ["email"] - }, - "single_view": { - "view_type": "default", - "search_params": {}, - "summary_attributes": ["email"] - }, - "list_item": { - "summary_attributes": [], - "quick_actions": [], - "ui_config": {} - }, - "sharing": { - "show_sharing_button": true - } - }, - "capabilities": [ - { - "id": "d5839b94-ba20-4225-a78e-76951d352bd6", - "name": "customer_messaging", - "title": "Messaging", - "attributes": [], - "_purpose": ["taxonomy-slug:classification-slug"], - "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], - "app_id": "123e4567-e89b-12d3-a456-426614174000", - "ui_config": {}, - "ui_hooks": [], - "feature_flag": "FF_MY_FEATURE_FLAG", - "settings_flag": [], - "schemas": [] - } - ], - "group_settings": [ - { - "id": "e18a532b-ae79-4d86-a6a5-e5dbfb579d14", - "label": "Contact Details", - "expanded": true, - "order": 1 - }, - { - "id": "e9a1ae28-27ba-4fa0-a79c-e279cc5c4a6e", - "label": "Address Details", - "expanded": false, - "order": 2, - "info_tooltip_title": {} - } - ], - "layout_settings": { - "grid_gap": "string", - "grid_template_columns": "string" - }, - "dialog_config": {}, - "attributes": [ - { - "name": "email", - "type": "email", - "label": "Email", - "required": true - }, - { - "name": "first_name", - "type": "string", - "label": "First Name" - } - ], - "_purpose": ["string"], - "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], - "explicit_search_mappings": { - "image": { - "type": "keyword", - "index": false - } - }, - "group_headlines": [ - { - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "name": "string", - "label": "string", - "layout": "string", - "group": "string", - "order": 0, - "type": "headline", - "enable_divider": false, - "divider": "top_divider", - "_purpose": ["taxonomy-slug:classification-slug"], - "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] - } - ] -} -``` - -
- ---- - ### `listAvailableCapabilities` List available capabilities for schema @@ -1504,9 +1231,11 @@ epilot entity listAvailableCapabilities -p slug=contact --jsonata 'results[0]' "name": "customer_messaging", "title": "Messaging", "attributes": [], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": {}, "ui_config": {}, "ui_hooks": [], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -1577,7 +1306,8 @@ epilot entity listSchemaBlueprints --jsonata 'results[0]' "_purpose": ["string"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "explicit_search_mappings": {}, - "group_headlines": [] + "group_headlines": [], + "conditions": [] } ] } @@ -1612,6 +1342,7 @@ epilot entity searchEntities \ "from": 0, "size": 10, "hydrate": false, + "anonymize": false, "fields": ["_id", "_title", "first_name", "account", "!account.*._files", "**._product"], "aggs": { "contact-count-per-tag": { @@ -1737,6 +1468,7 @@ epilot entity listEntities \ "from": 0, "size": 10, "hydrate": false, + "anonymize": false, "fields": ["_id", "_title", "first_name", "account", "!account.*._files", "**._product"], "aggs": { "contact-count-per-tag": { @@ -2333,6 +2065,8 @@ Gets Entity by id. | `id` | path | string (uuid) | Yes | Entity id | | `slug` | path | string | Yes | Entity Type | | `hydrate` | query | boolean | No | When true, enables entity hydration to resolve nested $relation & $relation_ref references in-place. | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `fields` | query | string[] | No | List of entity fields to include in results | | `apply_changesets` | query | boolean | No | When true, applies pending changeset proposed values in-place on the response entity. The response includes both the hydrated values and the raw _changesets field. @@ -2546,6 +2280,8 @@ Gets Entity and relations by id. | `id` | path | string (uuid) | Yes | Entity id | | `slug` | path | string | Yes | Entity Type | | `hydrate` | query | boolean | No | When true, enables entity hydration to resolve nested $relation & $relation_ref references in-place. | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | **Sample Call** @@ -2885,6 +2621,8 @@ Autocomplete entity attributes | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `input` | query | string | No | Input to autocomplete | | `attribute` | query | string | Yes | Autocomplete attribute | | `slug` | query | string | No | Limit results to entity schema | @@ -3051,6 +2789,8 @@ Get activity by id | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `id` | path | string (ulid) | Yes | Activity Id | | `operations_size` | query | number | No | Maximum number of operations to include in response (default: 10) | @@ -3109,7 +2849,9 @@ epilot entity getActivity -p id=01F130Q52Q6MWSNS8N2AVXV4JN --jsonata '$' "params": {}, "payload": {}, "diff": {}, - "_workflow_origin": {} + "source_context": {}, + "_workflow_origin": {}, + "_automation_chain": ["string"] } ] } @@ -3345,6 +3087,8 @@ Returns all pending changesets for an entity. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `slug` | path | string | Yes | Entity Type | | `id` | path | string (uuid) | Yes | Entity id | @@ -3389,6 +3133,8 @@ Get activity feed for an entity | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `slug` | path | string | Yes | Entity Type | | `id` | path | string (uuid) | Yes | Entity id | | `after` | query | string (date-time) | No | Get activities strictly after this timestamp. Cannot be used with 'before', 'start_date', 'end_date', or 'preset_range'. | @@ -3462,6 +3208,8 @@ Returns 1st level direct relations for an entity. | `slug` | path | string | Yes | Entity Type | | `id` | path | string (uuid) | Yes | Entity id | | `hydrate` | query | boolean | No | When true, enables entity hydration to resolve nested $relation & $relation_ref references in-place. | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `include_reverse` | query | boolean | No | When true, includes reverse relations in response (other entities pointing to this entity) | | `from` | query | number | No | Starting page number | | `size` | query | number | No | Number of results to return per page | @@ -3683,6 +3431,8 @@ Returns 1st level direct relations for an entity with pagination. | `slug` | path | string | Yes | Entity Type | | `id` | path | string (uuid) | Yes | Entity id | | `hydrate` | query | boolean | No | When true, enables entity hydration to resolve nested $relation & $relation_ref references in-place. | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `query` | query | string | No | Input to filter search results | | `include_reverse` | query | boolean | No | When true, includes reverse relations in response (other entities pointing to this entity) | | `from` | query | number | No | Starting page number | @@ -3765,6 +3515,8 @@ Returns 1st level direct relations for an entity with pagination. | `slug` | path | string | Yes | Entity Type | | `id` | path | string (uuid) | Yes | Entity id | | `hydrate` | query | boolean | No | When true, enables entity hydration to resolve nested $relation & $relation_ref references in-place. | +| `anonymize` | query | boolean | No | When true, anonymizes PII in the response: identifiers (names, emails, phone numbers, IBANs) are replaced +with deterministic pseudonyms (stable within an org), addresses are generalized to postal code | | `include_reverse` | query | boolean | No | When true, includes reverse relations in response (other entities pointing to this entity) *It gets overriden by mode query parameter.* | @@ -4032,6 +3784,7 @@ epilot entity exportEntities \ "from": 0, "size": 10, "hydrate": false, + "anonymize": false, "fields": ["_id", "_title", "first_name", "account", "!account.*._files", "**._product"], "aggs": { "contact-count-per-tag": { @@ -4097,6 +3850,35 @@ epilot entity importEntities --jsonata '$' --- +### `abortEntityImport` + +Asks a running import to stop. Rows already imported are **kept** - this is a stop, not a + +`POST /v1/entity:abortImport` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `job_id` | query | string | Yes | The ID of the import job to stop. | +| `schema` | query | string | Yes | Slug of the schema being imported, used to authorize the caller. | + +**Sample Call** + +```bash +epilot entity abortEntityImport \ + -p job_id=abc123 \ + -p schema=example +``` + +With JSONata filter: + +```bash +epilot entity abortEntityImport -p job_id=abc123 -p schema=example --jsonata '$' +``` + +--- + ### `listSavedViews` Get the Saved Views based on the schema @@ -4111,6 +3893,7 @@ Get the Saved Views based on the schema | `sort` | query | string | No | The sort key to use if present | | `from` | query | number | No | | | `size` | query | number | No | Number of saved views to return | +| `q` | query | string | No | Free-text search on the saved view name (case-insensitive) | | `fields` | query | string[] | No | | **Sample Call** @@ -4142,7 +3925,8 @@ epilot entity listSavedViews --jsonata 'results[0]' "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4193,7 +3977,8 @@ epilot entity createSavedView \ "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4239,7 +4024,8 @@ epilot entity createSavedView --jsonata '$' "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4308,7 +4094,8 @@ epilot entity getSavedView -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4368,7 +4155,8 @@ epilot entity updateSavedView \ "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4420,7 +4208,8 @@ epilot entity updateSavedView -p id=123e4567-e89b-12d3-a456-426614174000 --jsona "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4477,7 +4266,8 @@ epilot entity patchSavedView \ "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4529,7 +4319,8 @@ epilot entity patchSavedView -p id=123e4567-e89b-12d3-a456-426614174000 --jsonat "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -4620,7 +4411,8 @@ epilot entity listFavoriteViewsForUser --jsonata 'results[0]' "shared": true, "isFavoritedBy": ["11701"], "created_by": { - "user_id": "10598" + "user_id": "10598", + "org_id": "739224" }, "ui_config": { "filters": { @@ -5837,6 +5629,7 @@ epilot entity createSchemaAttribute \ "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -5844,6 +5637,7 @@ epilot entity createSchemaAttribute \ "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": { @@ -5921,6 +5715,7 @@ epilot entity createSchemaAttribute --jsonata '$' "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -5928,6 +5723,7 @@ epilot entity createSchemaAttribute --jsonata '$' "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": { @@ -6028,6 +5824,7 @@ epilot entity getSchemaAttribute -p composite_id=contact:97644baa-083f-4e49-9188 "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6035,6 +5832,7 @@ epilot entity getSchemaAttribute -p composite_id=contact:97644baa-083f-4e49-9188 "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": { @@ -6126,6 +5924,7 @@ epilot entity putSchemaAttribute \ "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6133,6 +5932,7 @@ epilot entity putSchemaAttribute \ "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": { @@ -6216,6 +6016,7 @@ epilot entity putSchemaAttribute -p composite_id=contact:97644baa-083f-4e49-9188 "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6223,6 +6024,7 @@ epilot entity putSchemaAttribute -p composite_id=contact:97644baa-083f-4e49-9188 "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": { @@ -6323,6 +6125,7 @@ epilot entity deleteSchemaAttribute -p composite_id=contact:97644baa-083f-4e49-9 "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6330,6 +6133,7 @@ epilot entity deleteSchemaAttribute -p composite_id=contact:97644baa-083f-4e49-9 "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": { @@ -6418,6 +6222,7 @@ epilot entity createSchemaCapability \ "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6425,6 +6230,7 @@ epilot entity createSchemaCapability \ "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6457,6 +6263,7 @@ epilot entity createSchemaCapability \ "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6464,6 +6271,7 @@ epilot entity createSchemaCapability \ "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6483,9 +6291,13 @@ epilot entity createSchemaCapability \ "type": "link" } ], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": { + "journey_id": "adjust-installment-flow" + }, "ui_config": { "is_filterable": true }, @@ -6502,7 +6314,10 @@ epilot entity createSchemaCapability \ "icon": "email", "disabled": true, "header": true, - "requiredPermission": {} + "requiredPermission": {}, + "default_visible": false, + "default_width": "half_width", + "pricing_tier_setting": "entity_address_maps" } ], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -6554,6 +6369,7 @@ epilot entity createSchemaCapability --jsonata '$' "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6561,6 +6377,7 @@ epilot entity createSchemaCapability --jsonata '$' "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6593,6 +6410,7 @@ epilot entity createSchemaCapability --jsonata '$' "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6600,6 +6418,7 @@ epilot entity createSchemaCapability --jsonata '$' "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6619,9 +6438,13 @@ epilot entity createSchemaCapability --jsonata '$' "type": "link" } ], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": { + "journey_id": "adjust-installment-flow" + }, "ui_config": { "is_filterable": true }, @@ -6638,7 +6461,10 @@ epilot entity createSchemaCapability --jsonata '$' "icon": "email", "disabled": true, "header": true, - "requiredPermission": {} + "requiredPermission": {}, + "default_visible": false, + "default_width": "half_width", + "pricing_tier_setting": "entity_address_maps" } ], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -6713,6 +6539,7 @@ epilot entity getSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6720,6 +6547,7 @@ epilot entity getSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6752,6 +6580,7 @@ epilot entity getSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6759,6 +6588,7 @@ epilot entity getSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6778,9 +6608,13 @@ epilot entity getSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "type": "link" } ], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": { + "journey_id": "adjust-installment-flow" + }, "ui_config": { "is_filterable": true }, @@ -6797,7 +6631,10 @@ epilot entity getSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "icon": "email", "disabled": true, "header": true, - "requiredPermission": {} + "requiredPermission": {}, + "default_visible": false, + "default_width": "half_width", + "pricing_tier_setting": "entity_address_maps" } ], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -6863,6 +6700,7 @@ epilot entity putSchemaCapability \ "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6870,6 +6708,7 @@ epilot entity putSchemaCapability \ "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6902,6 +6741,7 @@ epilot entity putSchemaCapability \ "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -6909,6 +6749,7 @@ epilot entity putSchemaCapability \ "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -6928,9 +6769,13 @@ epilot entity putSchemaCapability \ "type": "link" } ], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": { + "journey_id": "adjust-installment-flow" + }, "ui_config": { "is_filterable": true }, @@ -6947,7 +6792,10 @@ epilot entity putSchemaCapability \ "icon": "email", "disabled": true, "header": true, - "requiredPermission": {} + "requiredPermission": {}, + "default_visible": false, + "default_width": "half_width", + "pricing_tier_setting": "entity_address_maps" } ], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -7005,6 +6853,7 @@ epilot entity putSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -7012,6 +6861,7 @@ epilot entity putSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -7044,6 +6894,7 @@ epilot entity putSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -7051,6 +6902,7 @@ epilot entity putSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -7070,9 +6922,13 @@ epilot entity putSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "type": "link" } ], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": { + "journey_id": "adjust-installment-flow" + }, "ui_config": { "is_filterable": true }, @@ -7089,7 +6945,10 @@ epilot entity putSchemaCapability -p composite_id=contact:97644baa-083f-4e49-918 "icon": "email", "disabled": true, "header": true, - "requiredPermission": {} + "requiredPermission": {}, + "default_visible": false, + "default_width": "half_width", + "pricing_tier_setting": "entity_address_maps" } ], "feature_flag": "FF_MY_FEATURE_FLAG", @@ -7164,6 +7023,7 @@ epilot entity deleteSchemaCapability -p composite_id=contact:97644baa-083f-4e49- "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -7171,6 +7031,7 @@ epilot entity deleteSchemaCapability -p composite_id=contact:97644baa-083f-4e49- "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -7203,6 +7064,7 @@ epilot entity deleteSchemaCapability -p composite_id=contact:97644baa-083f-4e49- "required": false, "readonly": false, "deprecated": false, + "overridable_attribute": false, "default_value": {}, "group": "string", "order": 0, @@ -7210,6 +7072,7 @@ epilot entity deleteSchemaCapability -p composite_id=contact:97644baa-083f-4e49- "hide_label": true, "icon": "string", "render_condition": "string", + "data_classification": "public", "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "constraints": {}, @@ -7229,9 +7092,13 @@ epilot entity deleteSchemaCapability -p composite_id=contact:97644baa-083f-4e49- "type": "link" } ], + "overridable_attribute": false, "_purpose": ["taxonomy-slug:classification-slug"], "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "app_id": "123e4567-e89b-12d3-a456-426614174000", + "adjust_installment": { + "journey_id": "adjust-installment-flow" + }, "ui_config": { "is_filterable": true }, @@ -7248,7 +7115,10 @@ epilot entity deleteSchemaCapability -p composite_id=contact:97644baa-083f-4e49- "icon": "email", "disabled": true, "header": true, - "requiredPermission": {} + "requiredPermission": {}, + "default_visible": false, + "default_width": "half_width", + "pricing_tier_setting": "entity_address_maps" } ], "feature_flag": "FF_MY_FEATURE_FLAG", diff --git a/packages/cli/docs/environments.md b/packages/cli/docs/environments.md index f9eb1ba5b..df929d55b 100644 --- a/packages/cli/docs/environments.md +++ b/packages/cli/docs/environments.md @@ -37,12 +37,12 @@ epilot environments listEnvironmentVariables **environments** - [`listEnvironmentVariables`](#listenvironmentvariables) — List all environment variables for the organization. Returns metadata only, no secret values. -- [`createEnvironmentVariable`](#createenvironmentvariable) — Create a new environment variable or secret for the organization. +- [`createEnvironmentVariable`](#createenvironmentvariable) — Create a new environment variable or secret for the organization. If `group` is provided and the group does not yet exis - [`listEnvironmentGroups`](#listenvironmentgroups) — List all environment groups for the organization. -- [`putEnvironmentGroup`](#putenvironmentgroup) — PUT /v1/environments/groups/{name} +- [`putEnvironmentGroup`](#putenvironmentgroup) — Create or update an environment group by name. Acts as an upsert — creates the group if it does not exist. - [`deleteEnvironmentGroup`](#deleteenvironmentgroup) — Deletes a group. Variables assigned to this group become ungrouped. -- [`getEnvironmentVariable`](#getenvironmentvariable) — Get an environment variable by key. Returns value only for String type, omitted for SecretString. -- [`updateEnvironmentVariable`](#updateenvironmentvariable) — Create or update an environment variable. Acts as an upsert — creates the variable if it does not exist. +- [`getEnvironmentVariable`](#getenvironmentvariable) — Get an environment variable by key. Returns value for non-secret types, omitted for SecretString. +- [`updateEnvironmentVariable`](#updateenvironmentvariable) — Create or update an environment variable. Acts as an upsert — creates the variable if it does not exist. If `group` is p - [`deleteEnvironmentVariable`](#deleteenvironmentvariable) — Delete an environment variable by key. ### `listEnvironmentVariables` @@ -75,6 +75,7 @@ epilot environments listEnvironmentVariables --jsonata 'items[0]' "description": "string", "group": "string", "value": "string", + "protected": true, "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z" } @@ -88,7 +89,7 @@ epilot environments listEnvironmentVariables --jsonata 'items[0]' ### `createEnvironmentVariable` -Create a new environment variable or secret for the organization. +Create a new environment variable or secret for the organization. If `group` is provided and the group does not yet exis `POST /v1/environments` @@ -98,7 +99,7 @@ Create a new environment variable or secret for the organization. ```bash epilot environments createEnvironmentVariable \ - -d '{"key":"string","type":"String","description":"string","group":"string","value":"string"}' + -d '{"key":"string","type":"String","description":"string","group":"string","value":"string","protected":true}' ``` Using stdin pipe: @@ -123,6 +124,7 @@ epilot environments createEnvironmentVariable --jsonata 'key' "description": "string", "group": "string", "value": "string", + "protected": true, "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z" } @@ -172,6 +174,8 @@ epilot environments listEnvironmentGroups --jsonata 'items[0]' ### `putEnvironmentGroup` +Create or update an environment group by name. Acts as an upsert — creates the group if it does not exist. + `PUT /v1/environments/groups/{name}` **Parameters** @@ -259,7 +263,7 @@ epilot environments deleteEnvironmentGroup -p name=example --jsonata '$' ### `getEnvironmentVariable` -Get an environment variable by key. Returns value only for String type, omitted for SecretString. +Get an environment variable by key. Returns value for non-secret types, omitted for SecretString. `GET /v1/environments/{key}` @@ -298,6 +302,7 @@ epilot environments getEnvironmentVariable -p key=example --jsonata 'key' "description": "string", "group": "string", "value": "string", + "protected": true, "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z" } @@ -309,7 +314,7 @@ epilot environments getEnvironmentVariable -p key=example --jsonata 'key' ### `updateEnvironmentVariable` -Create or update an environment variable. Acts as an upsert — creates the variable if it does not exist. +Create or update an environment variable. Acts as an upsert — creates the variable if it does not exist. If `group` is p `PUT /v1/environments/{key}` @@ -326,7 +331,7 @@ Create or update an environment variable. Acts as an upsert — creates the vari ```bash epilot environments updateEnvironmentVariable \ -p key=example \ - -d '{"type":"String","value":"string","description":"string","group":"string"}' + -d '{"type":"String","value":"string","description":"string","group":"string","protected":true}' ``` Using positional args for path parameters: @@ -357,6 +362,7 @@ epilot environments updateEnvironmentVariable -p key=example --jsonata 'key' "description": "string", "group": "string", "value": "string", + "protected": true, "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z" } diff --git a/packages/cli/docs/event-catalog.md b/packages/cli/docs/event-catalog.md index 8b940ae7f..b7f9ff346 100644 --- a/packages/cli/docs/event-catalog.md +++ b/packages/cli/docs/event-catalog.md @@ -37,8 +37,12 @@ epilot event-catalog listEvents **Event Catalog** - [`listEvents`](#listevents) — Retrieve list of available business events +- [`createCustomEvent`](#createcustomevent) — Reserve an org-scoped custom event name and persist its immutable v1.0 draft definition. - [`getEvent`](#getevent) — Retrieve the configuration of a specific business event - [`patchEvent`](#patchevent) — Update the configuration of a specific business event for the organization +- [`deprecateCustomEvent`](#deprecatecustomevent) — Soft-deprecate an org-scoped custom event. Definitions and v1.0 history remain readable. +- [`previewCustomEvent`](#previewcustomevent) — Assemble and fully validate a persisted custom-event draft without publishing it. +- [`publishCustomEventDefinition`](#publishcustomeventdefinition) — Conditionally activate an immutable custom-event v1.0 definition. - [`getEventJSONSchema`](#geteventjsonschema) — Retrieve the JSON Schema of a specific business event. Pass an optional - [`getEventExample`](#geteventexample) — Generate a sample event payload based on the event's JSON Schema. Pass an - [`listEventVersions`](#listeventversions) — List every known version of an event, along with the `latest` @@ -99,11 +103,186 @@ epilot event-catalog listEvents --jsonata 'results[0]' "operation": ["createEntity", "updateEntity"], "schema": ["contact", "contract", "order"], "attribute": ["email", "phone", "status"], - "purpose": ["Kündigung", "Umzug/Auszug"] + "purpose": ["Kündigung", "Umzug/Auszug"], + "purpose_filters": [ + { + "id": "string", + "display_name": "string" + } + ] }, "enabled": true, "auto_trigger": true, - "automation_trigger": true + "automation_trigger": true, + "automation_trigger_only": true, + "automation_trigger_seed_node": "ticket", + "event_origin": "builtin", + "mapping": { + "mode": "guided", + "jsonata": "string" + }, + "lineage": { + "base_event_name": "string", + "base_event_version": "string" + }, + "success_criteria": [ + { + "entity_schema": "contract", + "attribute": "installment_amount" + }, + { + "entity_schema": "billing_account", + "attribute": "due_date" + } + ] + } + ] +} +``` + +
+ +--- + +### `createCustomEvent` + +Reserve an org-scoped custom event name and persist its immutable v1.0 draft definition. + +`POST /v1/events` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot event-catalog createCustomEvent +``` + +With request body: + +```bash +epilot event-catalog createCustomEvent \ + -d '{ + "event_name": "string", + "event_title": "string", + "event_description": "string", + "event_tags": ["string"], + "schema_fields": {}, + "entity_graph": { + "nodes": [ + { + "id": "contact", + "schema": "contact", + "cardinality": "one", + "fields": ["_id", "_title", "first_name", "account", "!account.*._files", "**._product"] + } + ], + "edges": [ + { + "from": "contact", + "to": "billing_account" + } + ] + }, + "entity_operation": { + "operation": ["createEntity", "updateEntity"], + "schema": ["contact", "contract", "order"], + "attribute": ["email", "phone", "status"], + "purpose": ["Kündigung", "Umzug/Auszug"], + "purpose_filters": [ + { + "id": "string", + "display_name": "string" + } + ] + }, + "automation_trigger": true, + "mapping": { + "mode": "guided", + "jsonata": "string" + }, + "lineage": { + "base_event_name": "string", + "base_event_version": "string" + }, + "example": {} +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot event-catalog createCustomEvent +``` + +With JSONata filter: + +```bash +epilot event-catalog createCustomEvent --jsonata '$' +``` + +
+Sample Response + +```json +{ + "event_name": "AddMeterReading", + "event_title": "Add Meter Reading", + "event_description": "Triggered when a new meter reading is added", + "event_version": "1.0", + "event_status": "active", + "event_tags": ["builtin", "metering", "erp"], + "schema_fields": {}, + "entity_graph": { + "nodes": [ + { + "id": "contact", + "schema": "contact", + "cardinality": "one", + "fields": ["_id", "_title", "first_name", "account", "!account.*._files", "**._product"] + } + ], + "edges": [ + { + "from": "contact", + "to": "billing_account" + } + ] + }, + "entity_operation": { + "operation": ["createEntity", "updateEntity"], + "schema": ["contact", "contract", "order"], + "attribute": ["email", "phone", "status"], + "purpose": ["Kündigung", "Umzug/Auszug"], + "purpose_filters": [ + { + "id": "string", + "display_name": "string" + } + ] + }, + "enabled": true, + "auto_trigger": true, + "automation_trigger": true, + "automation_trigger_only": true, + "automation_trigger_seed_node": "ticket", + "event_origin": "builtin", + "mapping": { + "mode": "guided", + "jsonata": "string" + }, + "lineage": { + "base_event_name": "string", + "base_event_version": "string" + }, + "success_criteria": [ + { + "entity_schema": "contract", + "attribute": "installment_amount" + }, + { + "entity_schema": "billing_account", + "attribute": "due_date" } ] } @@ -176,11 +355,38 @@ epilot event-catalog getEvent -p event_name=example --jsonata '$' "operation": ["createEntity", "updateEntity"], "schema": ["contact", "contract", "order"], "attribute": ["email", "phone", "status"], - "purpose": ["Kündigung", "Umzug/Auszug"] + "purpose": ["Kündigung", "Umzug/Auszug"], + "purpose_filters": [ + { + "id": "string", + "display_name": "string" + } + ] }, "enabled": true, "auto_trigger": true, - "automation_trigger": true + "automation_trigger": true, + "automation_trigger_only": true, + "automation_trigger_seed_node": "ticket", + "event_origin": "builtin", + "mapping": { + "mode": "guided", + "jsonata": "string" + }, + "lineage": { + "base_event_name": "string", + "base_event_version": "string" + }, + "success_criteria": [ + { + "entity_schema": "contract", + "attribute": "installment_amount" + }, + { + "entity_schema": "billing_account", + "attribute": "due_date" + } + ] } ``` @@ -206,15 +412,33 @@ Update the configuration of a specific business event for the organization ```bash epilot event-catalog patchEvent \ - -p event_name=example + -p event_name=example \ + -d '{"enabled":true,"auto_trigger":true,"success_criteria":[{"entity_schema":"contract","attribute":"installment_amount"}]}' ``` -With request body: +Using positional args for path parameters: ```bash -epilot event-catalog patchEvent \ - -p event_name=example \ - -d '{ +epilot event-catalog patchEvent example +``` + +Using stdin pipe: + +```bash +cat body.json | epilot event-catalog patchEvent -p event_name=example +``` + +With JSONata filter: + +```bash +epilot event-catalog patchEvent -p event_name=example --jsonata '$' +``` + +
+Sample Response + +```json +{ "event_name": "AddMeterReading", "event_title": "Add Meter Reading", "event_description": "Triggered when a new meter reading is added", @@ -242,30 +466,189 @@ epilot event-catalog patchEvent \ "operation": ["createEntity", "updateEntity"], "schema": ["contact", "contract", "order"], "attribute": ["email", "phone", "status"], - "purpose": ["Kündigung", "Umzug/Auszug"] + "purpose": ["Kündigung", "Umzug/Auszug"], + "purpose_filters": [ + { + "id": "string", + "display_name": "string" + } + ] }, "enabled": true, "auto_trigger": true, - "automation_trigger": true + "automation_trigger": true, + "automation_trigger_only": true, + "automation_trigger_seed_node": "ticket", + "event_origin": "builtin", + "mapping": { + "mode": "guided", + "jsonata": "string" + }, + "lineage": { + "base_event_name": "string", + "base_event_version": "string" + }, + "success_criteria": [ + { + "entity_schema": "contract", + "attribute": "installment_amount" + }, + { + "entity_schema": "billing_account", + "attribute": "due_date" + } + ] +} +``` + +
+ +--- + +### `deprecateCustomEvent` + +Soft-deprecate an org-scoped custom event. Definitions and v1.0 history remain readable. + +`DELETE /v1/events/{event_name}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_name` | path | string | Yes | | + +**Sample Call** + +```bash +epilot event-catalog deprecateCustomEvent \ + -p event_name=example +``` + +Using positional args for path parameters: + +```bash +epilot event-catalog deprecateCustomEvent example +``` + +With JSONata filter: + +```bash +epilot event-catalog deprecateCustomEvent -p event_name=example --jsonata '$' +``` + +--- + +### `previewCustomEvent` + +Assemble and fully validate a persisted custom-event draft without publishing it. + +`POST /v1/events/{event_name}:preview` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_name` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot event-catalog previewCustomEvent \ + -p event_name=example +``` + +With request body: + +```bash +epilot event-catalog previewCustomEvent \ + -p event_name=example \ + -d '{ + "seed": { + "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "ticket" + }, + "_trigger_source_type": "automation", + "_trigger_source": "execution-id/action-id" }' ``` Using positional args for path parameters: ```bash -epilot event-catalog patchEvent example +epilot event-catalog previewCustomEvent example ``` Using stdin pipe: ```bash -cat body.json | epilot event-catalog patchEvent -p event_name=example +cat body.json | epilot event-catalog previewCustomEvent -p event_name=example ``` With JSONata filter: ```bash -epilot event-catalog patchEvent -p event_name=example --jsonata '$' +epilot event-catalog previewCustomEvent -p event_name=example --jsonata 'payload' +``` + +
+Sample Response + +```json +{ + "payload": {}, + "errors": [ + { + "path": "string", + "message": "string" + } + ] +} +``` + +
+ +--- + +### `publishCustomEventDefinition` + +Conditionally activate an immutable custom-event v1.0 definition. + +`POST /v1/events/{event_name}:publish` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `event_name` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot event-catalog publishCustomEventDefinition \ + -p event_name=example \ + -d '{"enabled":true,"auto_trigger":true,"base_auto_trigger_enabled":true}' +``` + +Using positional args for path parameters: + +```bash +epilot event-catalog publishCustomEventDefinition example +``` + +Using stdin pipe: + +```bash +cat body.json | epilot event-catalog publishCustomEventDefinition -p event_name=example +``` + +With JSONata filter: + +```bash +epilot event-catalog publishCustomEventDefinition -p event_name=example --jsonata '$' ```
@@ -300,11 +683,38 @@ epilot event-catalog patchEvent -p event_name=example --jsonata '$' "operation": ["createEntity", "updateEntity"], "schema": ["contact", "contract", "order"], "attribute": ["email", "phone", "status"], - "purpose": ["Kündigung", "Umzug/Auszug"] + "purpose": ["Kündigung", "Umzug/Auszug"], + "purpose_filters": [ + { + "id": "string", + "display_name": "string" + } + ] }, "enabled": true, "auto_trigger": true, - "automation_trigger": true + "automation_trigger": true, + "automation_trigger_only": true, + "automation_trigger_seed_node": "ticket", + "event_origin": "builtin", + "mapping": { + "mode": "guided", + "jsonata": "string" + }, + "lineage": { + "base_event_name": "string", + "base_event_version": "string" + }, + "success_criteria": [ + { + "entity_schema": "contract", + "attribute": "installment_amount" + }, + { + "entity_schema": "billing_account", + "attribute": "due_date" + } + ] } ``` @@ -840,12 +1250,10 @@ epilot event-catalog triggerEvent \ -d '{ "seed": { "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "node_id": "string" + "node_id": "ticket" }, - "fields": {}, - "skip_hydration": ["string"], - "_trigger_source_type": "string", - "_trigger_source": "string" + "_trigger_source_type": "automation", + "_trigger_source": "execution-id/action-id" }' ``` diff --git a/packages/cli/docs/file.md b/packages/cli/docs/file.md index 7b28846ac..715a13b86 100644 --- a/packages/cli/docs/file.md +++ b/packages/cli/docs/file.md @@ -45,7 +45,14 @@ epilot file uploadFileV2 - [`downloadFiles`](#downloadfiles) — Bulk generate pre-signed download URLs for multiple files in a single request. - [`createZipJob`](#createzipjob) — Create a background job to ZIP multiple files and send a download link via email. - [`getZipJob`](#getzipjob) — Get the status of a ZIP job -- [`generateFileSummary`](#generatefilesummary) — Request AI generation of preview and short summaries for a file entity. +- [`getFileSummary`](#getfilesummary) — Get summary text for a file entity together with the current summary job status when available. +- [`getFileSummaryFeedback`](#getfilesummaryfeedback) — Get the authenticated user's feedback for the current generated file summary. +- [`putFileSummaryFeedback`](#putfilesummaryfeedback) — Upsert thumbs up/down feedback for the current generated file summary. +- [`createFileSummaryJob`](#createfilesummaryjob) — Create or return the current AI summary job for a file entity. +- [`getCurrentFileSummaryJob`](#getcurrentfilesummaryjob) — Get the latest AI summary job for the file entity's current source. +- [`getFileSummaryJob`](#getfilesummaryjob) — Get an AI summary job by id. +- [`generateFileSummary`](#generatefilesummary) — Compatibility alias for creating or returning the current AI summary job for a file entity. +- [`getFileText`](#getfiletext) — Get the plain-text representation of a file entity. Returns status `not_ready` while text is being prepared and `unsuppo - [`verifyCustomDownloadUrl`](#verifycustomdownloadurl) — Verify that a custom download URL is valid and has not expired. - [`uploadFilePublic`](#uploadfilepublic) — Create a pre-signed S3 URL for uploading a file without authentication. @@ -178,14 +185,16 @@ epilot file saveFileV2 \ "mime_type": "application/pdf", "size_bytes": 1234, "readable_size": "1.2 MB", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8", "access_control": "private", "public_url": "https://epilot-prod-user-content.s3.eu-central-1.amazonaws.com/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf", "custom_download_url": "https://some-api-url.com/download?file_id=123", + "custom_download_url_auth": "token", "preview_summary_de": "string", "short_summary_de": "string", "preview_summary_en": "string", "short_summary_en": "string", - "summary_status": "processing", + "file_summary_execution_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "s3ref": {} }' ``` @@ -219,14 +228,16 @@ epilot file saveFileV2 --jsonata '$' "mime_type": "application/pdf", "size_bytes": 1234, "readable_size": "1.2 MB", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8", "access_control": "private", "public_url": "https://epilot-prod-user-content.s3.eu-central-1.amazonaws.com/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf", "custom_download_url": "https://some-api-url.com/download?file_id=123", + "custom_download_url_auth": "token", "preview_summary_de": "string", "short_summary_de": "string", "preview_summary_en": "string", "short_summary_en": "string", - "summary_status": "processing", + "file_summary_execution_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "source_url": "https://productengineer-content.s3.eu-west-1.amazonaws.com/product-engineer-checklist.pdf", "s3ref": {}, "versions": [ @@ -235,7 +246,8 @@ epilot file saveFileV2 --jsonata '$' "filename": "document.pdf", "size_bytes": 1234, "readable_size": "1.2 MB", - "mime_type": "image/jpeg" + "mime_type": "image/jpeg", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8" } ], "_updated_at": "1970-01-01T00:00:00.000Z", @@ -310,14 +322,16 @@ epilot file getFile -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata '$' "mime_type": "application/pdf", "size_bytes": 1234, "readable_size": "1.2 MB", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8", "access_control": "private", "public_url": "https://epilot-prod-user-content.s3.eu-central-1.amazonaws.com/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf", "custom_download_url": "https://some-api-url.com/download?file_id=123", + "custom_download_url_auth": "token", "preview_summary_de": "string", "short_summary_de": "string", "preview_summary_en": "string", "short_summary_en": "string", - "summary_status": "processing", + "file_summary_execution_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "source_url": "https://productengineer-content.s3.eu-west-1.amazonaws.com/product-engineer-checklist.pdf", "s3ref": {}, "versions": [ @@ -326,7 +340,8 @@ epilot file getFile -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata '$' "filename": "document.pdf", "size_bytes": 1234, "readable_size": "1.2 MB", - "mime_type": "image/jpeg" + "mime_type": "image/jpeg", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8" } ], "_updated_at": "1970-01-01T00:00:00.000Z", @@ -401,14 +416,16 @@ epilot file deleteFile -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata '$' "mime_type": "application/pdf", "size_bytes": 1234, "readable_size": "1.2 MB", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8", "access_control": "private", "public_url": "https://epilot-prod-user-content.s3.eu-central-1.amazonaws.com/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf", "custom_download_url": "https://some-api-url.com/download?file_id=123", + "custom_download_url_auth": "token", "preview_summary_de": "string", "short_summary_de": "string", "preview_summary_en": "string", "short_summary_en": "string", - "summary_status": "processing", + "file_summary_execution_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "source_url": "https://productengineer-content.s3.eu-west-1.amazonaws.com/product-engineer-checklist.pdf", "s3ref": {}, "versions": [ @@ -417,7 +434,8 @@ epilot file deleteFile -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata '$' "filename": "document.pdf", "size_bytes": 1234, "readable_size": "1.2 MB", - "mime_type": "image/jpeg" + "mime_type": "image/jpeg", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8" } ], "_updated_at": "1970-01-01T00:00:00.000Z", @@ -454,6 +472,9 @@ Generate a pre-signed download URL for a file. | `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | | `version` | query | number | No | Index of the file version to download (0 = latest) | | `attachment` | query | boolean | No | Controls the Content-Disposition header. Set to `true` to trigger browser download dialog, `false` to display inline. | +| `x-track` | query | object | No | Download-attribution channel. Provide any number of query parameters whose names +start with `x-track-` (e.g. `x-track-portal-id`, `x-track-contact-id`); each is signed +into the pre-signed URL verbatim | **Sample Call** @@ -497,7 +518,10 @@ Generate a pre-signed download URL for a file using its S3 reference. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `s3_key` | query | string | Yes | The S3 object key | +| `s3_key` | query | string | Yes | The S3 object key, as returned in `s3ref.key` of the file entity. + +Note: object keys store the filename segment percent-encoded. For example, a file named +`Straße 1.pdf` is stored under the key `.../S | | `s3_bucket` | query | string | Yes | The S3 bucket name | | `attachment` | query | boolean | No | Controls the Content-Disposition header. Set to `true` to trigger browser download dialog, `false` to display inline. | @@ -505,14 +529,14 @@ Generate a pre-signed download URL for a file using its S3 reference. ```bash epilot file downloadS3File \ - -p s3_key=123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf \ + -p s3_key=123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/Stra%C3%9Fe%201.pdf \ -p s3_bucket=epilot-prod-user-content ``` With JSONata filter: ```bash -epilot file downloadS3File -p s3_key=123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf -p s3_bucket=epilot-prod-user-content --jsonata 'download_url' +epilot file downloadS3File -p s3_key=123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/Stra%C3%9Fe%201.pdf -p s3_bucket=epilot-prod-user-content --jsonata 'download_url' ```
@@ -661,9 +685,301 @@ epilot file getZipJob -p job_id=123e4567-e89b-12d3-a456-426614174000 --jsonata ' --- +### `getFileSummary` + +Get summary text for a file entity together with the current summary job status when available. + +`GET /v1/files/{id}/summary` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | + +**Sample Call** + +```bash +epilot file getFileSummary \ + -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +Using positional args for path parameters: + +```bash +epilot file getFileSummary ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +With JSONata filter: + +```bash +epilot file getFileSummary -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata 'status' +``` + +
+Sample Response + +```json +{ + "status": "queued", + "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "feedback_available": true, + "preview_summary_de": "string", + "short_summary_de": "string", + "preview_summary_en": "string", + "short_summary_en": "string" +} +``` + +
+ +--- + +### `getFileSummaryFeedback` + +Get the authenticated user's feedback for the current generated file summary. + +`GET /v1/files/{id}/summary/feedback` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | + +**Sample Call** + +```bash +epilot file getFileSummaryFeedback \ + -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +Using positional args for path parameters: + +```bash +epilot file getFileSummaryFeedback ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +With JSONata filter: + +```bash +epilot file getFileSummaryFeedback -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata 'feedback' +``` + +
+Sample Response + +```json +{ + "feedback": { + "rating": "up", + "comment": "string", + "user_id": "string", + "submitted_at": "1970-01-01T00:00:00.000Z" + } +} +``` + +
+ +--- + +### `putFileSummaryFeedback` + +Upsert thumbs up/down feedback for the current generated file summary. + +`PUT /v1/files/{id}/summary/feedback` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot file putFileSummaryFeedback \ + -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 \ + -d '{"rating":"up","comment":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot file putFileSummaryFeedback ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot file putFileSummaryFeedback -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +With JSONata filter: + +```bash +epilot file putFileSummaryFeedback -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata 'feedback' +``` + +
+Sample Response + +```json +{ + "feedback": { + "rating": "up", + "comment": "string", + "user_id": "string", + "submitted_at": "1970-01-01T00:00:00.000Z" + } +} +``` + +
+ +--- + +### `createFileSummaryJob` + +Create or return the current AI summary job for a file entity. + +`POST /v1/files/{id}/summary-jobs` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | + +**Sample Call** + +```bash +epilot file createFileSummaryJob \ + -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +Using positional args for path parameters: + +```bash +epilot file createFileSummaryJob ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +With JSONata filter: + +```bash +epilot file createFileSummaryJob -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata '$' +``` + +--- + +### `getCurrentFileSummaryJob` + +Get the latest AI summary job for the file entity's current source. + +`GET /v1/files/{id}/summary-jobs/current` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | + +**Sample Call** + +```bash +epilot file getCurrentFileSummaryJob \ + -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +Using positional args for path parameters: + +```bash +epilot file getCurrentFileSummaryJob ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +With JSONata filter: + +```bash +epilot file getCurrentFileSummaryJob -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata 'job_id' +``` + +
+Sample Response + +```json +{ + "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "file_id": "ef7d985c-2385-44f4-9c71-ae06a52264f8", + "status": "queued", + "error": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "completed_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `getFileSummaryJob` + +Get an AI summary job by id. + +`GET /v1/files/{id}/summary-jobs/{job_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | +| `job_id` | path | string (uuid) | Yes | The UUID of the summary job | + +**Sample Call** + +```bash +epilot file getFileSummaryJob \ + -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 \ + -p job_id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot file getFileSummaryJob ef7d985c-2385-44f4-9c71-ae06a52264f8 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot file getFileSummaryJob -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 -p job_id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'job_id' +``` + +
+Sample Response + +```json +{ + "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "file_id": "ef7d985c-2385-44f4-9c71-ae06a52264f8", + "status": "queued", + "error": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "completed_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + ### `generateFileSummary` -Request AI generation of preview and short summaries for a file entity. +Compatibility alias for creating or returning the current AI summary job for a file entity. `POST /v1/files/{id}/summary:generate` @@ -694,6 +1010,53 @@ epilot file generateFileSummary -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jso --- +### `getFileText` + +Get the plain-text representation of a file entity. Returns status `not_ready` while text is being prepared and `unsuppo + +`GET /v1/files/{id}/text` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string \| string (uuid) | Yes | The UUID of the file entity | + +**Sample Call** + +```bash +epilot file getFileText \ + -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +Using positional args for path parameters: + +```bash +epilot file getFileText ef7d985c-2385-44f4-9c71-ae06a52264f8 +``` + +With JSONata filter: + +```bash +epilot file getFileText -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 --jsonata 'status' +``` + +
+Sample Response + +```json +{ + "status": "ready", + "text": "string", + "truncated": true, + "total_chars": 0 +} +``` + +
+ +--- + ### `previewFile` Generate a thumbnail preview for a file entity. @@ -1387,14 +1750,16 @@ epilot file getFilesInCollection -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 -p c "mime_type": "application/pdf", "size_bytes": 1234, "readable_size": "1.2 MB", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8", "access_control": "private", "public_url": "https://epilot-prod-user-content.s3.eu-central-1.amazonaws.com/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf", "custom_download_url": "https://some-api-url.com/download?file_id=123", + "custom_download_url_auth": "token", "preview_summary_de": "string", "short_summary_de": "string", "preview_summary_en": "string", "short_summary_en": "string", - "summary_status": "processing", + "file_summary_execution_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "source_url": "https://productengineer-content.s3.eu-west-1.amazonaws.com/product-engineer-checklist.pdf", "s3ref": {}, "versions": [ @@ -1403,7 +1768,8 @@ epilot file getFilesInCollection -p id=ef7d985c-2385-44f4-9c71-ae06a52264f8 -p c "filename": "document.pdf", "size_bytes": 1234, "readable_size": "1.2 MB", - "mime_type": "image/jpeg" + "mime_type": "image/jpeg", + "etag": "9bb58f26192e4ba00f01e2e7b136bbd8" } ], "_updated_at": "1970-01-01T00:00:00.000Z", diff --git a/packages/cli/docs/index.md b/packages/cli/docs/index.md index 0f8f00409..d801c747f 100644 --- a/packages/cli/docs/index.md +++ b/packages/cli/docs/index.md @@ -15,53 +15,53 @@ npx epilot auth login | Access Token API | `epilot access-token` | 9 | [access-token.md](./access-token.md) | | Address API | `epilot address` | 3 | [address.md](./address.md) | | Address Suggestions API | `epilot address-suggestions` | 4 | [address-suggestions.md](./address-suggestions.md) | -| AI Agents API - OpenAPI 3.0 | `epilot ai-agents` | 12 | [ai-agents.md](./ai-agents.md) | -| App API | `epilot app` | 32 | [app.md](./app.md) | +| AI Agents API | `epilot ai-agents` | 21 | [ai-agents.md](./ai-agents.md) | +| App API | `epilot app` | 36 | [app.md](./app.md) | | Audit Log | `epilot audit-logs` | 2 | [audit-logs.md](./audit-logs.md) | -| Automation API | `epilot automation` | 16 | [automation.md](./automation.md) | -| Billing API | `epilot billing` | 10 | [billing.md](./billing.md) | -| Blueprint Manifest API | `epilot blueprint-manifest` | 61 | [blueprint-manifest.md](./blueprint-manifest.md) | -| Calendar API | `epilot calendar` | 4 | [calendar.md](./calendar.md) | +| Automation API | `epilot automation` | 17 | [automation.md](./automation.md) | +| Billing API | `epilot billing` | 14 | [billing.md](./billing.md) | +| Blueprint Manifest API | `epilot blueprint-manifest` | 73 | [blueprint-manifest.md](./blueprint-manifest.md) | +| Calendar API | `epilot calendar` | 28 | [calendar.md](./calendar.md) | | Configuration Hub API | `epilot configuration-hub` | 21 | [configuration-hub.md](./configuration-hub.md) | | Consent API | `epilot consent` | 3 | [consent.md](./consent.md) | -| Portal API | `epilot customer-portal` | 149 | [customer-portal.md](./customer-portal.md) | -| Dashboard API | `epilot dashboard` | 7 | [dashboard.md](./dashboard.md) | +| Portal API | `epilot customer-portal` | 167 | [customer-portal.md](./customer-portal.md) | +| Dashboard API | `epilot dashboard` | 18 | [dashboard.md](./dashboard.md) | | Data Governance API | `epilot data-governance` | 10 | [data-governance.md](./data-governance.md) | -| Deduplication API | `epilot deduplication` | 3 | [deduplication.md](./deduplication.md) | -| Design Builder API v2 | `epilot design` | 13 | [design.md](./design.md) | +| Deduplication API | `epilot deduplication` | 10 | [deduplication.md](./deduplication.md) | +| Design Builder API v2 | `epilot design` | 14 | [design.md](./design.md) | | Document API | `epilot document` | 3 | [document.md](./document.md) | -| Messaging Settings API | `epilot email-settings` | 36 | [email-settings.md](./email-settings.md) | +| Messaging Settings API | `epilot email-settings` | 48 | [email-settings.md](./email-settings.md) | | Email template API | `epilot email-template` | 7 | [email-template.md](./email-template.md) | -| Entity API | `epilot entity` | 87 | [entity.md](./entity.md) | +| Entity API | `epilot entity` | 88 | [entity.md](./entity.md) | | Entity Mapping API | `epilot entity-mapping` | 12 | [entity-mapping.md](./entity-mapping.md) | | Environments API | `epilot environments` | 8 | [environments.md](./environments.md) | -| Event Catalog API | `epilot event-catalog` | 10 | [event-catalog.md](./event-catalog.md) | -| File API | `epilot file` | 30 | [file.md](./file.md) | +| Event Catalog API | `epilot event-catalog` | 14 | [event-catalog.md](./event-catalog.md) | +| File API | `epilot file` | 37 | [file.md](./file.md) | | Iban API | `epilot iban` | 1 | [iban.md](./iban.md) | -| Integration Toolkit API | `epilot integration-toolkit` | 51 | [integration-toolkit.md](./integration-toolkit.md) | -| Journey API | `epilot journey` | 16 | [journey.md](./journey.md) | +| Integration Toolkit API | `epilot integration-toolkit` | 70 | [integration-toolkit.md](./integration-toolkit.md) | +| Journey API | `epilot journey` | 18 | [journey.md](./journey.md) | | Kanban API | `epilot kanban` | 10 | [kanban.md](./kanban.md) | -| Message API | `epilot message` | 52 | [message.md](./message.md) | -| Metering API | `epilot metering` | 20 | [metering.md](./metering.md) | +| Message API | `epilot message` | 54 | [message.md](./message.md) | +| Metering API | `epilot metering` | 21 | [metering.md](./metering.md) | | Notes API | `epilot notes` | 14 | [notes.md](./notes.md) | | Notification API | `epilot notification` | 14 | [notification.md](./notification.md) | | Organization API | `epilot organization` | 8 | [organization.md](./organization.md) | | Partner Directory API | `epilot partner-directory` | 17 | [partner-directory.md](./partner-directory.md) | | Permissions API | `epilot permissions` | 13 | [permissions.md](./permissions.md) | -| Pricing API | `epilot pricing` | 22 | [pricing.md](./pricing.md) | +| Pricing API | `epilot pricing` | 34 | [pricing.md](./pricing.md) | | Pricing Tier API | `epilot pricing-tier` | 1 | [pricing-tier.md](./pricing-tier.md) | | Purpose API | `epilot purpose` | 6 | [purpose.md](./purpose.md) | | Query API | `epilot query` | 21 | [query.md](./query.md) | | Sandbox API | `epilot sandbox` | 7 | [sandbox.md](./sandbox.md) | | Sharing API | `epilot sharing` | 12 | [sharing.md](./sharing.md) | -| Snapshot API | `epilot snapshot` | 9 | [snapshot.md](./snapshot.md) | +| Snapshot API | `epilot snapshot` | 12 | [snapshot.md](./snapshot.md) | | Submission API | `epilot submission` | 2 | [submission.md](./submission.md) | -| Targeting API | `epilot targeting` | 12 | [targeting.md](./targeting.md) | +| Targeting API | `epilot targeting` | 16 | [targeting.md](./targeting.md) | | Template Variables API | `epilot template-variables` | 12 | [template-variables.md](./template-variables.md) | -| User API | `epilot user` | 41 | [user.md](./user.md) | +| User API | `epilot user` | 42 | [user.md](./user.md) | | Validation Rules API | `epilot validation-rules` | 7 | [validation-rules.md](./validation-rules.md) | | Webhooks | `epilot webhooks` | 15 | [webhooks.md](./webhooks.md) | -| Workflows Executions | `epilot workflow` | 24 | [workflow.md](./workflow.md) | +| Workflows Executions | `epilot workflow` | 25 | [workflow.md](./workflow.md) | | Workflows Definitions | `epilot workflow-definition` | 22 | [workflow-definition.md](./workflow-definition.md) | ## Global Flags diff --git a/packages/cli/docs/integration-toolkit.md b/packages/cli/docs/integration-toolkit.md index 735dc9250..c897ce80f 100644 --- a/packages/cli/docs/integration-toolkit.md +++ b/packages/cli/docs/integration-toolkit.md @@ -40,6 +40,7 @@ epilot integration-toolkit acknowledgeTracking - [`triggerErp`](#triggererp) — Triggers the ERP integration process - [`processErpUpdatesEventsV3`](#processerpupdateseventsv3) — Handles updates from ERP systems using integration_id directly. - [`simulateMappingV2`](#simulatemappingv2) — Test v2.0 mapping configuration by transforming a payload using the provided mapping rules without persisting data. +- [`simulateDirect`](#simulatedirect) — Dry run for direct-mode payloads: validates a `DirectPayload` against a `direct: true` - [`simulateMapping`](#simulatemapping) — Test mapping configuration by transforming a payload using the provided mapping rules without persisting data. **integrations** @@ -56,17 +57,25 @@ epilot integration-toolkit acknowledgeTracking - [`updateUseCase`](#updateusecase) — Update an existing use case configuration - [`deleteUseCase`](#deleteusecase) — Delete a use case from an integration - [`listUseCaseHistory`](#listusecasehistory) — Retrieve historical versions of a use case's configuration. +- [`listDocumentationPages`](#listdocumentationpages) — Retrieve all documentation pages of an integration, without their markdown content. +- [`getDocumentationPage`](#getdocumentationpage) — Retrieve a single documentation page including its markdown content +- [`upsertDocumentationPage`](#upsertdocumentationpage) — Create or update the documentation page identified by docId. +- [`deleteDocumentationPage`](#deletedocumentationpage) — Delete a documentation page - [`listIntegrationsV2`](#listintegrationsv2) — Retrieve all integrations with embedded use cases for the authenticated organization - [`createIntegrationV2`](#createintegrationv2) — Create a new integration with embedded use cases. - [`getIntegrationV2`](#getintegrationv2) — Retrieve a specific integration with all its embedded use cases - [`updateIntegrationV2`](#updateintegrationv2) — Update an existing integration with embedded use cases. - [`deleteIntegrationV2`](#deleteintegrationv2) — Delete an integration and all its use cases +- [`listNotificationHistory`](#listnotificationhistory) — Returns the cursor-paginated, newest-first notification history for an +- [`testSendNotification`](#testsendnotification) — Renders and sends ONE representative notification of the requested kind/type to +- [`getNotificationStatus`](#getnotificationstatus) — Returns the live per-rule alert state and (for 'auto' rules) the current - [`getSecureProxyWhitelist`](#getsecureproxywhitelist) — Returns the current allowed_domains, allowed_ips, and vpc_mode for a secure_proxy use case. - [`updateSecureProxyWhitelist`](#updatesecureproxywhitelist) — Replaces allowed_domains and/or allowed_ips on a secure_proxy use case. - [`listSecureProxyWhitelistHistory`](#listsecureproxywhitelisthistory) — Returns the most recent USECASE_HISTORY entries for a secure_proxy use case, - [`setIntegrationAppMapping`](#setintegrationappmapping) — Creates or updates a mapping from an app/component to an integration. - [`deleteIntegrationAppMapping`](#deleteintegrationappmapping) — Removes a mapping from an app/component to an integration. - [`getOutboundStatus`](#getoutboundstatus) — Get the status of all outbound use cases for a specific integration. +- [`getEntitySyncStatus`](#getentitysyncstatus) — Get the inbound ERP sync status of an entity: when each integration last - [`pollOutboundMessages`](#polloutboundmessages) — Poll outbound messages for an integration's poll-mode use cases. - [`ackOutboundMessages`](#ackoutboundmessages) — Acknowledge polled outbound messages. Acks are validated against the - [`listOutboundDlqMessages`](#listoutbounddlqmessages) — List an integration's dead-lettered outbound queue messages @@ -87,6 +96,8 @@ epilot integration-toolkit acknowledgeTracking - [`getMonitoringStatsV2`](#getmonitoringstatsv2) — Get aggregated statistics from the unified erp_monitoring_v2 table. - [`getMonitoringTimeSeriesV2`](#getmonitoringtimeseriesv2) — Get time-series aggregated event counts from the unified erp_monitoring_v2 table. - [`getAssociatedMonitoringEvents`](#getassociatedmonitoringevents) — Returns all monitoring events sharing the same event_id, ordered chronologically. +- [`ingestExternalMonitoringEvents`](#ingestexternalmonitoringevents) — Ingest monitoring spans produced by an EXTERNAL system (e.g. an integration +- [`getMonitoringTraceByCorrelation`](#getmonitoringtracebycorrelation) — Returns the cross-system event trace for a `correlation_id`: every monitoring **proxy** - [`secureProxy`](#secureproxy) — Routes an HTTP request through a VPC with either static IP egress or VPN secure link access. @@ -94,6 +105,16 @@ epilot integration-toolkit acknowledgeTracking **managed-call** - [`managedCallExecute`](#managedcallexecute) — Execute a managed call operation synchronously. The slug in the path acts as the RPC method name. +**erp-imports** +- [`listErpImports`](#listerpimports) — List recent pricing-file import jobs for the org, newest first. +- [`createErpImport`](#createerpimport) — Register an already-uploaded file (S3 ref) as a pricing-file import job. Returns the job and a file preview. Nothing run +- [`getErpImport`](#geterpimport) — Get a pricing-file import job (status, counts, result links). +- [`deleteErpImport`](#deleteerpimport) — Remove an import and the file it owns. Allowed from any status: an import whose run is still in flight is stopped by the +- [`validateErpImport`](#validateerpimport) — Choose the use case to read this file with, and start the validate phase. +- [`suggestErpImportUseCases`](#suggesterpimportusecases) — Rank the org's inbound use cases against this file's columns — the input to the ranked picker ("matches 6 of your 7 colu +- [`executeErpImport`](#executeerpimport) — Confirm and run the write phase of a validated import. Only a READY job may be executed; any other status returns 409. +- [`abortErpImport`](#aborterpimport) — Ask a running import to stop. Valid while the job is VALIDATING or PROCESSING; any other status returns 409. + ### `acknowledgeTracking` Acknowledges an ERP tracking record by removing it from the tracking table, requires public authentication @@ -278,6 +299,7 @@ With request body: epilot integration-toolkit simulateMappingV2 \ -d '{ "event_configuration": { + "direct": true, "entities": [ {} ], @@ -311,7 +333,14 @@ epilot integration-toolkit simulateMappingV2 --jsonata 'entity_updates' { "entity_slug": "string", "unique_identifiers": {}, - "attributes": {} + "attributes": {}, + "pricing": { + "config": {}, + "data": [ + {} + ] + }, + "mode": "upsert" } ], "meter_readings_updates": [ @@ -322,7 +351,106 @@ epilot integration-toolkit simulateMappingV2 --jsonata 'entity_updates' "meter_counter": { "$entity_unique_ids": {} }, - "attributes": {} + "attributes": {}, + "mode": "upsert" + } + ], + "prune_scope_updates": [ + { + "entity_slug": "string", + "scope": { + "scope_mode": "relations", + "schema": "string", + "unique_ids": {}, + "query": {} + }, + "keep_unique_ids": [ + {} + ], + "deletion_mode": "delete" + } + ], + "meter_readings_prune_scope_updates": [ + { + "meter": { + "$entity_unique_ids": {} + }, + "meter_counter": { + "$entity_unique_ids": {} + }, + "keep_external_ids": ["string"], + "source": "string" + } + ], + "warnings": [ + { + "entity_schema": "string", + "field": "string", + "message": "string" + } + ] +} +``` + +
+ +--- + +### `simulateDirect` + +Dry run for direct-mode payloads: validates a `DirectPayload` against a `direct: true` + +`POST /v1/erp/updates/direct_simulation` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot integration-toolkit simulateDirect +``` + +With request body: + +```bash +epilot integration-toolkit simulateDirect \ + -d '{ + "event_configuration": { + "direct": true, + "entities": [ + {} + ], + "meter_readings": [ + {} + ] + }, + "payload": "string" +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit simulateDirect +``` + +With JSONata filter: + +```bash +epilot integration-toolkit simulateDirect --jsonata 'valid' +``` + +
+Sample Response + +```json +{ + "valid": true, + "errors": [ + { + "code": "DIRECT_PAYLOAD_INVALID", + "message": "string", + "operation_index": 0 } ], "warnings": [ @@ -331,6 +459,36 @@ epilot integration-toolkit simulateMappingV2 --jsonata 'entity_updates' "field": "string", "message": "string" } + ], + "entity_updates": [ + { + "entity_slug": "string", + "unique_identifiers": {}, + "attributes": {}, + "pricing": { + "config": {}, + "data": [ + {} + ] + }, + "mode": "upsert", + "unique_identifiers_metadata": {} + } + ], + "meter_reading_updates": [ + { + "meter": { + "$entity_unique_ids": {} + }, + "meter_counter": { + "$entity_unique_ids": {} + }, + "attributes": {}, + "mode": "upsert", + "_config": { + "reading_matching": "external_id" + } + } ] } ``` @@ -391,7 +549,14 @@ epilot integration-toolkit simulateMapping --jsonata 'entity_updates' { "entity_slug": "string", "unique_identifiers": {}, - "attributes": {} + "attributes": {}, + "pricing": { + "config": {}, + "data": [ + {} + ] + }, + "mode": "upsert" } ], "meter_readings_updates": [ @@ -402,7 +567,35 @@ epilot integration-toolkit simulateMapping --jsonata 'entity_updates' "meter_counter": { "$entity_unique_ids": {} }, - "attributes": {} + "attributes": {}, + "mode": "upsert" + } + ], + "prune_scope_updates": [ + { + "entity_slug": "string", + "scope": { + "scope_mode": "relations", + "schema": "string", + "unique_ids": {}, + "query": {} + }, + "keep_unique_ids": [ + {} + ], + "deletion_mode": "delete" + } + ], + "meter_readings_prune_scope_updates": [ + { + "meter": { + "$entity_unique_ids": {} + }, + "meter_counter": { + "$entity_unique_ids": {} + }, + "keep_external_ids": ["string"], + "source": "string" } ], "warnings": [ @@ -452,52 +645,10 @@ epilot integration-toolkit listIntegrations --jsonata 'integrations' "description": "string", "access_token_ids": ["string"], "app_ids": ["string"], - "environment_config": [ - { - "key": "string", - "label": "string", - "type": "String", - "description": "string", - "required": false, - "order": 0 - } - ], - "settings": { - "autoRefresh": { - "enabled": false, - "freshnessThresholdMinutes": 1 - } - }, + "environment_config": [], + "settings": {}, "integration_type": "erp", - "connector_config": { - "base_url": "string", - "auth": { - "type": "oauth2_client_credentials", - "token_url": "string", - "client_id": "string", - "client_secret": "string", - "scope": "string", - "audience": "string", - "resource": "string", - "body_params": {}, - "headers": {}, - "query_params": {}, - "api_key_header": "string", - "api_key": "string", - "token": "string" - }, - "types_versions": [ - { - "version": "string", - "package_name": "string", - "generated_at": "1970-01-01T00:00:00.000Z", - "generated_by": "string", - "status": "active" - } - ], - "latest_types_version": "string", - "latest_types_package_name": "string" - }, + "connector_config": {}, "protected": true, "_manifest": ["string"] } @@ -546,6 +697,16 @@ epilot integration-toolkit createIntegration \ "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -567,13 +728,7 @@ epilot integration-toolkit createIntegration \ "token": "string" }, "types_versions": [ - { - "version": "string", - "package_name": "string", - "generated_at": "1970-01-01T00:00:00.000Z", - "generated_by": "string", - "status": "active" - } + {} ], "latest_types_version": "string", "latest_types_package_name": "string" @@ -622,6 +777,16 @@ epilot integration-toolkit createIntegration --jsonata '$' "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -643,13 +808,7 @@ epilot integration-toolkit createIntegration --jsonata '$' "token": "string" }, "types_versions": [ - { - "version": "string", - "package_name": "string", - "generated_at": "1970-01-01T00:00:00.000Z", - "generated_by": "string", - "status": "active" - } + {} ], "latest_types_version": "string", "latest_types_package_name": "string" @@ -721,6 +880,16 @@ epilot integration-toolkit getIntegration -p integrationId=123e4567-e89b-12d3-a4 "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -742,13 +911,7 @@ epilot integration-toolkit getIntegration -p integrationId=123e4567-e89b-12d3-a4 "token": "string" }, "types_versions": [ - { - "version": "string", - "package_name": "string", - "generated_at": "1970-01-01T00:00:00.000Z", - "generated_by": "string", - "status": "active" - } + {} ], "latest_types_version": "string", "latest_types_package_name": "string" @@ -829,6 +992,16 @@ epilot integration-toolkit updateIntegration -p integrationId=123e4567-e89b-12d3 "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -850,13 +1023,7 @@ epilot integration-toolkit updateIntegration -p integrationId=123e4567-e89b-12d3 "token": "string" }, "types_versions": [ - { - "version": "string", - "package_name": "string", - "generated_at": "1970-01-01T00:00:00.000Z", - "generated_by": "string", - "status": "active" - } + {} ], "latest_types_version": "string", "latest_types_package_name": "string" @@ -986,6 +1153,7 @@ epilot integration-toolkit queryEvents -p integrationId=123e4567-e89b-12d3-a456- "format": "json", "payload": "string", "deduplication_id": "evt-2025-05-01-12345-create-bp", + "correlation_id": "bp-8f3a2c-7d4e-4b1a-9c2f-1e6d5a4b3c21", "use_case_id": "string" } ], @@ -1038,7 +1206,7 @@ cat body.json | epilot integration-toolkit replayEvents -p integrationId=123e456 With JSONata filter: ```bash -epilot integration-toolkit replayEvents -p integrationId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'event_ids' +epilot integration-toolkit replayEvents -p integrationId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'results[0]' ```
@@ -1046,6 +1214,15 @@ epilot integration-toolkit replayEvents -p integrationId=123e4567-e89b-12d3-a456 ```json { + "replayed": 2, + "results": [ + { + "event_id": "string", + "status": "success", + "replay_event_id": "string", + "message": "string" + } + ], "event_ids": ["string"] } ``` @@ -1099,6 +1276,7 @@ epilot integration-toolkit listUseCases -p integrationId=123e4567-e89b-12d3-a456 "type": "inbound", "enabled": true, "change_description": "string", + "changed_by": "string", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "configuration": {} @@ -1143,6 +1321,7 @@ epilot integration-toolkit createUseCase \ "enabled": true, "type": "inbound", "configuration": { + "direct": true, "entities": [ {} ], @@ -1183,9 +1362,11 @@ epilot integration-toolkit createUseCase -p integrationId=123e4567-e89b-12d3-a45 "type": "inbound", "enabled": true, "change_description": "string", + "changed_by": "string", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "configuration": { + "direct": true, "entities": [ {} ], @@ -1245,9 +1426,11 @@ epilot integration-toolkit getUseCase -p integrationId=123e4567-e89b-12d3-a456-4 "type": "inbound", "enabled": true, "change_description": "string", + "changed_by": "string", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "configuration": { + "direct": true, "entities": [ {} ], @@ -1298,6 +1481,7 @@ epilot integration-toolkit updateUseCase \ "change_description": "string", "type": "inbound", "configuration": { + "direct": true, "entities": [ {} ], @@ -1338,9 +1522,11 @@ epilot integration-toolkit updateUseCase -p integrationId=123e4567-e89b-12d3-a45 "type": "inbound", "enabled": true, "change_description": "string", + "changed_by": "string", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "configuration": { + "direct": true, "entities": [ {} ], @@ -1467,22 +1653,35 @@ epilot integration-toolkit listUseCaseHistory -p integrationId=123e4567-e89b-12d --- -### `listIntegrationsV2` +### `listDocumentationPages` -Retrieve all integrations with embedded use cases for the authenticated organization +Retrieve all documentation pages of an integration, without their markdown content. -`GET /v2/integrations` +`GET /v1/integrations/{integrationId}/documentation` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | **Sample Call** ```bash -epilot integration-toolkit listIntegrationsV2 +epilot integration-toolkit listDocumentationPages \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit listDocumentationPages 123e4567-e89b-12d3-a456-426614174000 ``` With JSONata filter: ```bash -epilot integration-toolkit listIntegrationsV2 --jsonata 'integrations' +epilot integration-toolkit listDocumentationPages -p integrationId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'pages' ```
@@ -1490,23 +1689,17 @@ epilot integration-toolkit listIntegrationsV2 --jsonata 'integrations' ```json { - "integrations": [ + "pages": [ { - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "orgId": "string", + "id": "general", + "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "scope": "integration", + "use_case_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "string", "created_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", "updated_at": "1970-01-01T00:00:00.000Z", - "name": "string", - "description": "string", - "access_token_ids": ["string"], - "app_ids": ["string"], - "environment_config": [], - "settings": {}, - "integration_type": "erp", - "connector_config": {}, - "protected": true, - "_manifest": ["string"], - "use_cases": [] + "updated_by": "string" } ] } @@ -1516,86 +1709,310 @@ epilot integration-toolkit listIntegrationsV2 --jsonata 'integrations' --- -### `createIntegrationV2` +### `getDocumentationPage` -Create a new integration with embedded use cases. +Retrieve a single documentation page including its markdown content -`POST /v2/integrations` +`GET /v1/integrations/{integrationId}/documentation/{docId}` -**Request Body** (required) +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | +| `docId` | path | string | Yes | 'general' for the integration-wide page, otherwise a use case ID | **Sample Call** ```bash -epilot integration-toolkit createIntegrationV2 +epilot integration-toolkit getDocumentationPage \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ + -p docId=123e4567-e89b-12d3-a456-426614174000 ``` -With request body: +Using positional args for path parameters: ```bash -epilot integration-toolkit createIntegrationV2 \ - -d '{ - "name": "string", - "description": "string", - "access_token_ids": ["string"], - "app_ids": ["string"], - "environment_config": [ - { - "key": "string", - "label": "string", - "type": "String", - "description": "string", - "required": false, - "order": 0 - } - ], - "settings": { - "autoRefresh": { - "enabled": false, - "freshnessThresholdMinutes": 1 - } - }, - "integration_type": "erp", - "connector_config": { - "base_url": "string", - "auth": { - "type": "oauth2_client_credentials", - "token_url": "string", - "client_id": "string", - "client_secret": "string", - "scope": "string", - "audience": "string", - "resource": "string", - "body_params": {}, - "headers": {}, - "query_params": {}, - "api_key_header": "string", - "api_key": "string", - "token": "string" - }, - "types_versions": [ - {} - ], - "latest_types_version": "string", - "latest_types_package_name": "string" - }, - "protected": true, - "_manifest": ["string"], - "use_cases": [ - { - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "name": "string", - "slug": "string", - "enabled": true, - "change_description": "string", - "type": "inbound", - "configuration": {} - } - ] -}' +epilot integration-toolkit getDocumentationPage 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 ``` -Using stdin pipe: +With JSONata filter: + +```bash +epilot integration-toolkit getDocumentationPage -p integrationId=123e4567-e89b-12d3-a456-426614174000 -p docId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "id": "general", + "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "scope": "integration", + "use_case_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_at": "1970-01-01T00:00:00.000Z", + "updated_by": "string", + "content": "string" +} +``` + +
+ +--- + +### `upsertDocumentationPage` + +Create or update the documentation page identified by docId. + +`PUT /v1/integrations/{integrationId}/documentation/{docId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | +| `docId` | path | string | Yes | 'general' for the integration-wide page, otherwise a use case ID | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot integration-toolkit upsertDocumentationPage \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ + -p docId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"title":"string","content":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit upsertDocumentationPage 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit upsertDocumentationPage -p integrationId=123e4567-e89b-12d3-a456-426614174000 -p docId=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit upsertDocumentationPage -p integrationId=123e4567-e89b-12d3-a456-426614174000 -p docId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "id": "general", + "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "scope": "integration", + "use_case_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_at": "1970-01-01T00:00:00.000Z", + "updated_by": "string", + "content": "string" +} +``` + +
+ +--- + +### `deleteDocumentationPage` + +Delete a documentation page + +`DELETE /v1/integrations/{integrationId}/documentation/{docId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | +| `docId` | path | string | Yes | 'general' for the integration-wide page, otherwise a use case ID | + +**Sample Call** + +```bash +epilot integration-toolkit deleteDocumentationPage \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ + -p docId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit deleteDocumentationPage 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit deleteDocumentationPage -p integrationId=123e4567-e89b-12d3-a456-426614174000 -p docId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'message' +``` + +
+Sample Response + +```json +{ + "message": "string" +} +``` + +
+ +--- + +### `listIntegrationsV2` + +Retrieve all integrations with embedded use cases for the authenticated organization + +`GET /v2/integrations` + +**Sample Call** + +```bash +epilot integration-toolkit listIntegrationsV2 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit listIntegrationsV2 --jsonata 'integrations' +``` + +
+Sample Response + +```json +{ + "integrations": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "orgId": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "name": "string", + "description": "string", + "access_token_ids": ["string"], + "app_ids": ["string"], + "environment_config": [], + "settings": {}, + "integration_type": "erp", + "connector_config": {}, + "protected": true, + "_manifest": ["string"], + "use_cases": [] + } + ] +} +``` + +
+ +--- + +### `createIntegrationV2` + +Create a new integration with embedded use cases. + +`POST /v2/integrations` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot integration-toolkit createIntegrationV2 +``` + +With request body: + +```bash +epilot integration-toolkit createIntegrationV2 \ + -d '{ + "name": "string", + "description": "string", + "access_token_ids": ["string"], + "app_ids": ["string"], + "environment_config": [ + { + "key": "string", + "label": "string", + "type": "String", + "description": "string", + "required": false, + "order": 0 + } + ], + "settings": { + "autoRefresh": { + "enabled": false, + "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" + } + }, + "integration_type": "erp", + "connector_config": { + "base_url": "string", + "auth": { + "type": "oauth2_client_credentials", + "token_url": "string", + "client_id": "string", + "client_secret": "string", + "scope": "string", + "audience": "string", + "resource": "string", + "body_params": {}, + "headers": {}, + "query_params": {}, + "api_key_header": "string", + "api_key": "string", + "token": "string" + }, + "types_versions": [ + {} + ], + "latest_types_version": "string", + "latest_types_package_name": "string" + }, + "protected": true, + "_manifest": ["string"], + "use_cases": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "slug": "string", + "enabled": true, + "change_description": "string", + "type": "inbound", + "configuration": {} + } + ] +}' +``` + +Using stdin pipe: ```bash cat body.json | epilot integration-toolkit createIntegrationV2 @@ -1634,6 +2051,16 @@ epilot integration-toolkit createIntegrationV2 --jsonata '$' "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -1671,6 +2098,7 @@ epilot integration-toolkit createIntegrationV2 --jsonata '$' "type": "inbound", "enabled": true, "change_description": "string", + "changed_by": "string", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "configuration": {} @@ -1741,6 +2169,16 @@ epilot integration-toolkit getIntegrationV2 -p integrationId=123e4567-e89b-12d3- "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -1778,6 +2216,7 @@ epilot integration-toolkit getIntegrationV2 -p integrationId=123e4567-e89b-12d3- "type": "inbound", "enabled": true, "change_description": "string", + "changed_by": "string", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "configuration": {} @@ -1835,6 +2274,16 @@ epilot integration-toolkit updateIntegrationV2 \ "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -1922,6 +2371,16 @@ epilot integration-toolkit updateIntegrationV2 -p integrationId=123e4567-e89b-12 "autoRefresh": { "enabled": false, "freshnessThresholdMinutes": 1 + }, + "notifications": { + "enabled": true, + "recipients": [], + "defaultChannels": {}, + "monitoredUseCases": ["string"], + "monitoredCodes": ["string"], + "rules": [], + "digest": {}, + "muteUntil": "1970-01-01T00:00:00.000Z" } }, "integration_type": "erp", @@ -1959,6 +2418,7 @@ epilot integration-toolkit updateIntegrationV2 -p integrationId=123e4567-e89b-12 "type": "inbound", "enabled": true, "change_description": "string", + "changed_by": "string", "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "configuration": {} @@ -2015,25 +2475,200 @@ epilot integration-toolkit deleteIntegrationV2 -p integrationId=123e4567-e89b-12 --- -### `getSecureProxyWhitelist` +### `listNotificationHistory` -Returns the current allowed_domains, allowed_ips, and vpc_mode for a secure_proxy use case. +Returns the cursor-paginated, newest-first notification history for an -`GET /v2/integrations/{integrationId}/use-cases/{useCaseId}/secure-proxy-whitelist` +`GET /v2/integrations/{integrationId}/notifications/history` **Parameters** | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | | `integrationId` | path | string (uuid) | Yes | The integration ID | -| `useCaseId` | path | string (uuid) | Yes | The use case ID | +| `cursor` | query | string | No | Opaque base64 pagination cursor returned as `next_cursor` by a prior page. | +| `limit` | query | number | No | Requested number of items to return. Values above 100 are accepted and clamped server-side to 100 (the enforcement point is the handler, not this schema), so a large value never 400s at the contract l | +| `type` | query | string | No | Optional notification type filter (e.g. `critical_error`, `error_threshold`). | **Sample Call** ```bash -epilot integration-toolkit getSecureProxyWhitelist \ - -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ - -p useCaseId=123e4567-e89b-12d3-a456-426614174000 +epilot integration-toolkit listNotificationHistory \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit listNotificationHistory 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit listNotificationHistory -p integrationId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'history' +``` + +
+Sample Response + +```json +{ + "history": [ + { + "id": "string", + "type": "string", + "state_transition": "string", + "severity": "error", + "title": "string", + "occurred_at": "1970-01-01T00:00:00.000Z", + "notified": true, + "suppressed_reason": "muted", + "recipients": ["string"], + "context": {}, + "created_at": "1970-01-01T00:00:00.000Z" + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `testSendNotification` + +Renders and sends ONE representative notification of the requested kind/type to + +`POST /v2/integrations/{integrationId}/notifications/test` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot integration-toolkit testSendNotification \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"kind":"alert","type":"string","channels":["email"]}' +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit testSendNotification 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit testSendNotification -p integrationId=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit testSendNotification -p integrationId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `getNotificationStatus` + +Returns the live per-rule alert state and (for 'auto' rules) the current + +`GET /v2/integrations/{integrationId}/notifications/status` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | +| `include` | query | "baseline_series" | No | Add `baseline_series` to also return all 168 hour-of-week buckets per +'auto' rule (heavier; omit for just the current-bucket markers). + | + +**Sample Call** + +```bash +epilot integration-toolkit getNotificationStatus \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit getNotificationStatus 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit getNotificationStatus -p integrationId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'health' +``` + +
+Sample Response + +```json +{ + "health": "healthy", + "evaluated_at": "1970-01-01T00:00:00.000Z", + "rules": [ + { + "rule_id": "string", + "state": "ok", + "last_fired_at": "1970-01-01T00:00:00.000Z", + "last_cleared_at": "1970-01-01T00:00:00.000Z", + "baseline": { + "is_mature": true, + "computed_at": "1970-01-01T00:00:00.000Z", + "median": 0, + "mad": 0, + "upper": 0, + "buckets": [ + { + "dow": 1, + "hour": 0, + "median": 0, + "mad": 0 + } + ] + } + } + ] +} +``` + +
+ +--- + +### `getSecureProxyWhitelist` + +Returns the current allowed_domains, allowed_ips, and vpc_mode for a secure_proxy use case. + +`GET /v2/integrations/{integrationId}/use-cases/{useCaseId}/secure-proxy-whitelist` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | +| `useCaseId` | path | string (uuid) | Yes | The use case ID | + +**Sample Call** + +```bash +epilot integration-toolkit getSecureProxyWhitelist \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ + -p useCaseId=123e4567-e89b-12d3-a456-426614174000 ``` Using positional args for path parameters: @@ -2633,7 +3268,78 @@ epilot integration-toolkit getOutboundStatus -p integrationId=123e4567-e89b-12d3 "last_ack_at": "1970-01-01T00:00:00.000Z", "blocked": true, "dlq_count": 0 - } + }, + "file_proxy": [ + { + "mapping_id": "string", + "use_case_slug": "string", + "resolved": true, + "target_use_case_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "target_enabled": true, + "target_updated_at": "1970-01-01T00:00:00.000Z", + "unresolved_reason": "not_found" + } + ] + } + ] +} +``` + +
+ +--- + +### `getEntitySyncStatus` + +Get the inbound ERP sync status of an entity: when each integration last + +`GET /v1/entities/{entityId}/sync-status` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `entityId` | path | string (uuid) | Yes | The entity ID | +| `integration_id` | query | string (uuid) | No | Narrow the result to a single integration | + +**Sample Call** + +```bash +epilot integration-toolkit getEntitySyncStatus \ + -p entityId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit getEntitySyncStatus 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit getEntitySyncStatus -p entityId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'entity_id' +``` + +
+Sample Response + +```json +{ + "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "sync_states": [ + { + "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "entity_slug": "contract", + "use_case_id": "string", + "last_synced_at": "1970-01-01T00:00:00.000Z", + "last_changed_at": "1970-01-01T00:00:00.000Z", + "last_operation": "create", + "last_event_id": "string", + "readings_last_synced_at": "1970-01-01T00:00:00.000Z", + "readings_last_operation": "upsert", + "readings_last_event_id": "string" } ] } @@ -3249,6 +3955,7 @@ epilot integration-toolkit getMonitoringStatsV2 \ "from_date": "2025-01-01T00:00:00Z", "to_date": "2025-01-31T23:59:59Z", "use_case_type": "inbound", + "use_case_types": ["inbound"], "group_by": "use_case_id", "source": "monitoring" }' @@ -3446,86 +4153,214 @@ epilot integration-toolkit getAssociatedMonitoringEvents -p integrationId=123e45 --- -### `listSecureProxies` +### `ingestExternalMonitoringEvents` -Lists all secure_proxy use cases across all integrations for the authenticated organization. +Ingest monitoring spans produced by an EXTERNAL system (e.g. an integration -`GET /v1/integrations/secure-proxies` +`POST /v2/integrations/{integrationId}/monitoring/external-events` -**Sample Call** +**Parameters** -```bash -epilot integration-toolkit listSecureProxies -``` +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | -With JSONata filter: +**Request Body** (required) + +**Sample Call** ```bash -epilot integration-toolkit listSecureProxies --jsonata 'secure_proxies' +epilot integration-toolkit ingestExternalMonitoringEvents \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 ``` -
-Sample Response +With request body: -```json -{ - "secure_proxies": [ +```bash +epilot integration-toolkit ingestExternalMonitoringEvents \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{ + "events": [ { - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "name": "string", - "slug": "string", - "enabled": true, - "vpc_mode": "static_ip", - "allowed_domains": ["string"], - "allowed_ips": ["string"], - "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "integration_name": "string" + "correlation_id": "string", + "level": "string", + "use_case_slug": "string", + "occurred_at": "1970-01-01T00:00:00.000Z", + "message": "string", + "detail": {} } ] -} +}' ``` -
+Using positional args for path parameters: + +```bash +epilot integration-toolkit ingestExternalMonitoringEvents 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit ingestExternalMonitoringEvents -p integrationId=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit ingestExternalMonitoringEvents -p integrationId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` --- -### `secureProxy` +### `getMonitoringTraceByCorrelation` -Routes an HTTP request through a VPC with either static IP egress or VPN secure link access. +Returns the cross-system event trace for a `correlation_id`: every monitoring -`POST /v1/secure-proxy` +`GET /v2/integrations/{integrationId}/monitoring/traces/{correlationId}` -**Request Body** (required) +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `integrationId` | path | string (uuid) | Yes | The integration ID | +| `correlationId` | path | string | Yes | The trace id (correlation_id) shared across systems | **Sample Call** ```bash -epilot integration-toolkit secureProxy +epilot integration-toolkit getMonitoringTraceByCorrelation \ + -p integrationId=123e4567-e89b-12d3-a456-426614174000 \ + -p correlationId=123e4567-e89b-12d3-a456-426614174000 ``` -With request body: +Using positional args for path parameters: ```bash -epilot integration-toolkit secureProxy \ - -d '{ - "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "use_case_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "use_case_slug": "string", - "url": "https://example.com/path", - "method": "GET", - "headers": {}, - "body": {}, - "response_type": "json" -}' +epilot integration-toolkit getMonitoringTraceByCorrelation 123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000 ``` -Using stdin pipe: +With JSONata filter: ```bash -cat body.json | epilot integration-toolkit secureProxy +epilot integration-toolkit getMonitoringTraceByCorrelation -p integrationId=123e4567-e89b-12d3-a456-426614174000 -p correlationId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'correlation_id' ``` -With JSONata filter: +
+Sample Response + +```json +{ + "correlation_id": "string", + "status": "success", + "started_at": "1970-01-01T00:00:00.000Z", + "ended_at": "1970-01-01T00:00:00.000Z", + "span_count": 0, + "truncated": true, + "spans": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "string", + "integration_id": "string", + "event_id": "string", + "correlation_id": "string", + "use_case_id": "string", + "use_case_type": "inbound", + "level": "success", + "code": "string", + "message": "string", + "detail": {}, + "created_at": "1970-01-01T00:00:00.000Z" + } + ], + "inbound_event": {} +} +``` + +
+ +--- + +### `listSecureProxies` + +Lists all secure_proxy use cases across all integrations for the authenticated organization. + +`GET /v1/integrations/secure-proxies` + +**Sample Call** + +```bash +epilot integration-toolkit listSecureProxies +``` + +With JSONata filter: + +```bash +epilot integration-toolkit listSecureProxies --jsonata 'secure_proxies' +``` + +
+Sample Response + +```json +{ + "secure_proxies": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "slug": "string", + "enabled": true, + "vpc_mode": "static_ip", + "allowed_domains": ["string"], + "allowed_ips": ["string"], + "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "integration_name": "string" + } + ] +} +``` + +
+ +--- + +### `secureProxy` + +Routes an HTTP request through a VPC with either static IP egress or VPN secure link access. + +`POST /v1/secure-proxy` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot integration-toolkit secureProxy +``` + +With request body: + +```bash +epilot integration-toolkit secureProxy \ + -d '{ + "integration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "use_case_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "use_case_slug": "string", + "url": "https://example.com/path", + "method": "GET", + "headers": {}, + "body": {}, + "response_type": "json" +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit secureProxy +``` + +With JSONata filter: ```bash epilot integration-toolkit secureProxy --jsonata 'status_code' @@ -3753,3 +4588,471 @@ epilot integration-toolkit commitTypes -p integrationId=123e4567-e89b-12d3-a456-
--- + +### `listErpImports` + +List recent pricing-file import jobs for the org, newest first. + +`GET /v2/erp/imports` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `limit` | query | number | No | Page size. Values above 100 are clamped to 100. | +| `cursor` | query | string | No | Opaque cursor from a prior page's `next_cursor`. | + +**Sample Call** + +```bash +epilot integration-toolkit listErpImports +``` + +With JSONata filter: + +```bash +epilot integration-toolkit listErpImports --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "import_id": "string", + "org_id": "string", + "created_by": "string", + "integration_id": "string", + "use_case_slug": "string", + "format": "csv", + "status": "PENDING", + "s3_input_ref": { + "bucket": "string", + "key": "string" + }, + "size_bytes": 0, + "column_count": 0, + "validation": { + "total_rows": 0, + "blocking": 0, + "warnings": 0, + "entities": {}, + "issues": [ + { + "code": "UNIQUE_ID_COLUMN_MISSING", + "severity": "warning", + "columns": [ + { + "name": "string", + "entity": "string" + } + ], + "row": 0 + } + ] + }, + "progress": { + "processed_rows": 0, + "total_rows": 0 + }, + "error": { + "code": "VALIDATION_BLOCKED", + "message": "string" + }, + "correlation_id": "string", + "activity_id": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `createErpImport` + +Register an already-uploaded file (S3 ref) as a pricing-file import job. Returns the job and a file preview. Nothing run + +`POST /v2/erp/imports` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot integration-toolkit createErpImport \ + -d '{"s3_reference":{"bucket":"string","key":"string"},"include_preview":false,"import_id":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit createErpImport +``` + +With JSONata filter: + +```bash +epilot integration-toolkit createErpImport --jsonata 'job' +``` + +
+Sample Response + +```json +{ + "job": { + "import_id": "string", + "org_id": "string", + "created_by": "string", + "integration_id": "string", + "use_case_slug": "string", + "format": "csv", + "status": "PENDING", + "s3_input_ref": { + "bucket": "string", + "key": "string" + }, + "size_bytes": 0, + "column_count": 0, + "validation": { + "total_rows": 0, + "blocking": 0, + "warnings": 0, + "entities": {}, + "issues": [ + { + "code": "UNIQUE_ID_COLUMN_MISSING", + "severity": "warning", + "columns": [ + { + "name": "string", + "entity": "string" + } + ], + "row": 0 + } + ] + }, + "progress": { + "processed_rows": 0, + "total_rows": 0 + }, + "error": { + "code": "VALIDATION_BLOCKED", + "message": "string" + }, + "correlation_id": "string", + "activity_id": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" + }, + "preview": { + "columns": ["string"], + "rows": [ + ["string"] + ] + } +} +``` + +
+ +--- + +### `getErpImport` + +Get a pricing-file import job (status, counts, result links). + +`GET /v2/erp/imports/{importId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `importId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot integration-toolkit getErpImport \ + -p importId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit getErpImport 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit getErpImport -p importId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'import_id' +``` + +
+Sample Response + +```json +{ + "import_id": "string", + "org_id": "string", + "created_by": "string", + "integration_id": "string", + "use_case_slug": "string", + "format": "csv", + "status": "PENDING", + "s3_input_ref": { + "bucket": "string", + "key": "string" + }, + "size_bytes": 0, + "column_count": 0, + "validation": { + "total_rows": 0, + "blocking": 0, + "warnings": 0, + "entities": {}, + "issues": [ + { + "code": "UNIQUE_ID_COLUMN_MISSING", + "severity": "warning", + "columns": [ + { + "name": "string", + "entity": "string" + } + ], + "row": 0 + } + ] + }, + "progress": { + "processed_rows": 0, + "total_rows": 0 + }, + "error": { + "code": "VALIDATION_BLOCKED", + "message": "string" + }, + "correlation_id": "string", + "activity_id": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteErpImport` + +Remove an import and the file it owns. Allowed from any status: an import whose run is still in flight is stopped by the + +`DELETE /v2/erp/imports/{importId}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `importId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot integration-toolkit deleteErpImport \ + -p importId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit deleteErpImport 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit deleteErpImport -p importId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `validateErpImport` + +Choose the use case to read this file with, and start the validate phase. + +`POST /v2/erp/imports/{importId}:validate` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `importId` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot integration-toolkit validateErpImport \ + -p importId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"integration_id":"string","use_case_slug":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit validateErpImport 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit validateErpImport -p importId=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit validateErpImport -p importId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `suggestErpImportUseCases` + +Rank the org's inbound use cases against this file's columns — the input to the ranked picker ("matches 6 of your 7 colu + +`POST /v2/erp/imports/{importId}:suggest-use-cases` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `importId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot integration-toolkit suggestErpImportUseCases \ + -p importId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit suggestErpImportUseCases 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit suggestErpImportUseCases -p importId=123e4567-e89b-12d3-a456-426614174000 --jsonata 'file_columns' +``` + +
+Sample Response + +```json +{ + "file_columns": 0, + "suggestions": [ + { + "integration_id": "string", + "integration_name": "string", + "use_case_slug": "string", + "use_case_name": "string", + "entity_types": 0, + "matched_columns": 0 + } + ] +} +``` + +
+ +--- + +### `executeErpImport` + +Confirm and run the write phase of a validated import. Only a READY job may be executed; any other status returns 409. + +`POST /v2/erp/imports/{importId}:execute` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `importId` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot integration-toolkit executeErpImport \ + -p importId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"ack_warnings":true}' +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit executeErpImport 123e4567-e89b-12d3-a456-426614174000 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot integration-toolkit executeErpImport -p importId=123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit executeErpImport -p importId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- + +### `abortErpImport` + +Ask a running import to stop. Valid while the job is VALIDATING or PROCESSING; any other status returns 409. + +`POST /v2/erp/imports/{importId}:abort` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `importId` | path | string | Yes | | + +**Sample Call** + +```bash +epilot integration-toolkit abortErpImport \ + -p importId=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot integration-toolkit abortErpImport 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot integration-toolkit abortErpImport -p importId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +``` + +--- diff --git a/packages/cli/docs/journey.md b/packages/cli/docs/journey.md index 05720ccf7..7315fb6f9 100644 --- a/packages/cli/docs/journey.md +++ b/packages/cli/docs/journey.md @@ -39,6 +39,8 @@ epilot journey getJourneysByOrgId -p id=123 - [`getJourneysByOrgId`](#getjourneysbyorgid) — Get all journeys by organization id - [`getJourney`](#getjourney) — Get journey by id. Private journeys requires valid private token to be passed - [`removeJourney`](#removejourney) — Remove journey by id +- [`getJourneyEnvironment`](#getjourneyenvironment) — Resolve the environment variables referenced by this journey. Only browser-safe value types are returned. +- [`getJourneyEnvironmentVariables`](#getjourneyenvironmentvariables) — List the organization's environment variables that a journey block may use as an options source. Only Map variables that - [`getJourneyProducts`](#getjourneyproducts) — Get products available in the journey by id. requires public journey token to be passed. - [`createJourney`](#createjourney) — Create a Journey - [`updateJourney`](#updatejourney) — Update a Journey @@ -150,6 +152,7 @@ epilot journey getJourney -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata ' "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -185,7 +188,8 @@ epilot journey getJourney -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata ' "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -206,6 +210,7 @@ epilot journey getJourney -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata ' "templateId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "targetedCustomer": "string", "description": "string", "organizationSettings": {}, @@ -223,20 +228,24 @@ epilot journey getJourney -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata ' "useAustrianLabels": true, "enableDarkMode": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "isPublished": true, "status": "string", "isActive": true, "savingProgress": { - "savingMode": "auto", - "supportedVersion": 0 + "mode": "auto", + "supportedRevision": 0 }, "thirdPartyCookies": true }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], @@ -289,6 +298,108 @@ epilot journey removeJourney -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonat --- +### `getJourneyEnvironment` + +Resolve the environment variables referenced by this journey. Only browser-safe value types are returned. + +`GET /v1/journey/configuration/{id}/environment` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `id` | path | string (uuid) | Yes | Journey ID bound to the supplied journey access token | + +**Sample Call** + +```bash +epilot journey getJourneyEnvironment \ + -p id=123e4567-e89b-12d3-a456-426614174000 +``` + +Using positional args for path parameters: + +```bash +epilot journey getJourneyEnvironment 123e4567-e89b-12d3-a456-426614174000 +``` + +With JSONata filter: + +```bash +epilot journey getJourneyEnvironment -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'items[0]' +``` + +
+Sample Response + +```json +{ + "items": [ + { + "datasourceId": "string", + "type": "Text", + "value": "string" + } + ], + "errors": [ + { + "datasourceId": "string", + "code": "not_found" + } + ] +} +``` + +
+ +--- + +### `getJourneyEnvironmentVariables` + +List the organization's environment variables that a journey block may use as an options source. Only Map variables that + +`GET /v1/journey/environment-variables` + +**Sample Call** + +```bash +epilot journey getJourneyEnvironmentVariables +``` + +With JSONata filter: + +```bash +epilot journey getJourneyEnvironmentVariables --jsonata 'items[0]' +``` + +
+Sample Response + +```json +{ + "items": [ + { + "key": "string", + "type": "Map", + "value": { + "fallbackLanguage": "de", + "options": [ + { + "key": "string", + "value": "string" + } + ] + }, + "description": "string" + } + ] +} +``` + +
+ +--- + ### `getJourneyProducts` Get products available in the journey by id. requires public journey token to be passed. @@ -395,6 +506,7 @@ epilot journey createJourney \ "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -430,7 +542,8 @@ epilot journey createJourney \ "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -451,6 +564,7 @@ epilot journey createJourney \ "templateId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "targetedCustomer": "string", "description": "string", "organizationSettings": {}, @@ -468,20 +582,24 @@ epilot journey createJourney \ "useAustrianLabels": true, "enableDarkMode": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "isPublished": true, "status": "string", "isActive": true, "savingProgress": { - "savingMode": "auto", - "supportedVersion": 0 + "mode": "auto", + "supportedRevision": 0 }, "thirdPartyCookies": true }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], @@ -520,6 +638,7 @@ epilot journey createJourney --jsonata '$' "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -555,7 +674,8 @@ epilot journey createJourney --jsonata '$' "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -576,6 +696,7 @@ epilot journey createJourney --jsonata '$' "templateId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "targetedCustomer": "string", "description": "string", "organizationSettings": {}, @@ -593,20 +714,24 @@ epilot journey createJourney --jsonata '$' "useAustrianLabels": true, "enableDarkMode": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "isPublished": true, "status": "string", "isActive": true, "savingProgress": { - "savingMode": "auto", - "supportedVersion": 0 + "mode": "auto", + "supportedRevision": 0 }, "thirdPartyCookies": true }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], @@ -657,6 +782,7 @@ epilot journey updateJourney \ "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -692,7 +818,8 @@ epilot journey updateJourney \ "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -713,6 +840,7 @@ epilot journey updateJourney \ "templateId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "targetedCustomer": "string", "description": "string", "organizationSettings": {}, @@ -730,20 +858,24 @@ epilot journey updateJourney \ "useAustrianLabels": true, "enableDarkMode": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "isPublished": true, "status": "string", "isActive": true, "savingProgress": { - "savingMode": "auto", - "supportedVersion": 0 + "mode": "auto", + "supportedRevision": 0 }, "thirdPartyCookies": true }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], @@ -833,6 +965,7 @@ epilot journey patchUpdateJourney --jsonata 'createdJourney' "templateId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "targetedCustomer": "string", "description": "string", "organizationSettings": {}, @@ -850,6 +983,7 @@ epilot journey patchUpdateJourney --jsonata 'createdJourney' "useAustrianLabels": true, "enableDarkMode": true, "accessMode": "PUBLIC", + "authGate": {}, "isPublished": true, "status": "string", "isActive": true, @@ -858,7 +992,8 @@ epilot journey patchUpdateJourney --jsonata 'createdJourney' }, "validationRules": { "block1": "rule123", - "block2": {} + "block2": ["rule456", "rule789"], + "block3": {} }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"], "createdBy": "string", @@ -950,9 +1085,22 @@ Builds document generated from a template with journey values." **Sample Call** +```bash +epilot journey generateDocument +``` + +With request body: + ```bash epilot journey generateDocument \ - -d '{"file_id":"1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p","context_data":{"additionalProperties":"string"},"language":"de"}' + -d '{ + "file_id": "1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p", + "context_data": { + "additionalProperties": "string" + }, + "context_entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "language": "de" +}' ``` Using stdin pipe: @@ -1055,6 +1203,7 @@ epilot journey createJourneyV2 \ "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -1090,7 +1239,8 @@ epilot journey createJourneyV2 \ "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -1109,6 +1259,7 @@ epilot journey createJourneyV2 \ "designId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "templateId": "string", "targetedCustomer": "string", "description": "string", @@ -1125,14 +1276,23 @@ epilot journey createJourneyV2 \ "useNewDesign": true, "thirdPartyCookies": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "enableDarkMode": true, - "isActive": true + "useAustrianLabels": true, + "isActive": true, + "savingProgress": { + "mode": "auto", + "supportedRevision": 0 + } }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] @@ -1167,6 +1327,7 @@ epilot journey createJourneyV2 --jsonata 'journeyId' "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -1202,7 +1363,8 @@ epilot journey createJourneyV2 --jsonata 'journeyId' "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -1221,6 +1383,7 @@ epilot journey createJourneyV2 --jsonata 'journeyId' "designId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "templateId": "string", "targetedCustomer": "string", "description": "string", @@ -1237,14 +1400,23 @@ epilot journey createJourneyV2 --jsonata 'journeyId' "useNewDesign": true, "thirdPartyCookies": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "enableDarkMode": true, - "isActive": true + "useAustrianLabels": true, + "isActive": true, + "savingProgress": { + "mode": "auto", + "supportedRevision": 0 + } }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] @@ -1285,6 +1457,7 @@ epilot journey updateJourneyV2 \ "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -1320,7 +1493,8 @@ epilot journey updateJourneyV2 \ "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -1339,6 +1513,7 @@ epilot journey updateJourneyV2 \ "designId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "templateId": "string", "targetedCustomer": "string", "description": "string", @@ -1355,14 +1530,23 @@ epilot journey updateJourneyV2 \ "useNewDesign": true, "thirdPartyCookies": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "enableDarkMode": true, - "isActive": true + "useAustrianLabels": true, + "isActive": true, + "savingProgress": { + "mode": "auto", + "supportedRevision": 0 + } }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] @@ -1397,6 +1581,7 @@ epilot journey updateJourneyV2 --jsonata 'journeyId' "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -1432,7 +1617,8 @@ epilot journey updateJourneyV2 --jsonata 'journeyId' "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -1451,6 +1637,7 @@ epilot journey updateJourneyV2 --jsonata 'journeyId' "designId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "templateId": "string", "targetedCustomer": "string", "description": "string", @@ -1467,14 +1654,23 @@ epilot journey updateJourneyV2 --jsonata 'journeyId' "useNewDesign": true, "thirdPartyCookies": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "enableDarkMode": true, - "isActive": true + "useAustrianLabels": true, + "isActive": true, + "savingProgress": { + "mode": "auto", + "supportedRevision": 0 + } }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] @@ -1528,6 +1724,7 @@ epilot journey patchUpdateJourneyV2 --jsonata 'journeyId' "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -1563,7 +1760,8 @@ epilot journey patchUpdateJourneyV2 --jsonata 'journeyId' "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -1582,6 +1780,7 @@ epilot journey patchUpdateJourneyV2 --jsonata 'journeyId' "designId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "templateId": "string", "targetedCustomer": "string", "description": "string", @@ -1598,14 +1797,23 @@ epilot journey patchUpdateJourneyV2 --jsonata 'journeyId' "useNewDesign": true, "thirdPartyCookies": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "enableDarkMode": true, - "isActive": true + "useAustrianLabels": true, + "isActive": true, + "savingProgress": { + "mode": "auto", + "supportedRevision": 0 + } }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] @@ -1646,7 +1854,7 @@ epilot journey getJourneyV2 509cdffe-424f-457a-95c2-9708c304ce77 With JSONata filter: ```bash -epilot journey getJourneyV2 -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata 'journeyId' +epilot journey getJourneyV2 -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata '$' ```
@@ -1665,6 +1873,7 @@ epilot journey getJourneyV2 -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata "showStepSubtitle": true, "showStepper": true, "showStepperLabels": true, + "stepperType": "numbers", "hideNextButton": true, "name": "string", "stepId": "string", @@ -1700,7 +1909,8 @@ epilot journey getJourneyV2 -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata "type": "string", "paramKey": "string", "isRequired": true, - "shouldLoadEntity": true + "shouldLoadEntity": true, + "description": "string" } ], "journey_type": "Sales template (Premium)", @@ -1719,6 +1929,7 @@ epilot journey getJourneyV2 -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata "designId": "string", "entityId": "string", "mappingsAutomationId": "string", + "newMappings": true, "templateId": "string", "targetedCustomer": "string", "description": "string", @@ -1735,14 +1946,23 @@ epilot journey getJourneyV2 -p id=509cdffe-424f-457a-95c2-9708c304ce77 --jsonata "useNewDesign": true, "thirdPartyCookies": true, "accessMode": "PUBLIC", + "authGate": { + "stepId": "string" + }, "enableDarkMode": true, - "isActive": true + "useAustrianLabels": true, + "isActive": true, + "savingProgress": { + "mode": "auto", + "supportedRevision": 0 + } }, "validationRules": { "block1": "rule123", - "block2": { - "field1": "rule456", - "field2": "rule789" + "block2": ["rule456", "rule789"], + "block3": { + "field1": "rule101", + "field2": ["rule102", "rule103"] } }, "_manifest": ["123e4567-e89b-12d3-a456-426614174000"] diff --git a/packages/cli/docs/message.md b/packages/cli/docs/message.md index bc552acf2..8de4d9ade 100644 --- a/packages/cli/docs/message.md +++ b/packages/cli/docs/message.md @@ -48,6 +48,7 @@ epilot message sendMessage - [`markReadMessageV2`](#markreadmessagev2) — Mark message as read within a scope - [`markUnreadMessage`](#markunreadmessage) — Mark message as unread - [`getUnread`](#getunread) — Get all unread messages by actor +- [`getUnreadCounts`](#getunreadcounts) — Unread counts for several named scopes in one request. - [`markUnreadMessageV2`](#markunreadmessagev2) — Mark message as unread within a scope - [`spamMessage`](#spammessage) — Mark a single message as spam. Also marks the parent thread as spam if all messages in the thread are spam. - [`unspamMessage`](#unspammessage) — Remove spam marking from a single message. Also removes spam from the parent thread if no other messages are spam. @@ -57,6 +58,7 @@ epilot message sendMessage - [`searchThreads`](#searchthreads) — Search for threads of email messages. - [`searchThreadsV2`](#searchthreadsv2) — Search for threads of email messages. - [`searchIds`](#searchids) — Return all thread id's that match a criteria +- [`getAssigneeWorkload`](#getassigneeworkload) — Return the open-thread workload for a set of user ids. - [`updateThread`](#updatethread) — Modify thread metadata - [`deleteThread`](#deletethread) — Immediately and permanently delete a thread. This operation cannot be undone. - [`moveThread`](#movethread) — Move thread to a different Inbox @@ -169,6 +171,7 @@ epilot message updateMessage --jsonata '$' "_updated_at": "2021-02-10T09:14:31.990Z", "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", @@ -222,7 +225,8 @@ epilot message updateMessage --jsonata '$' "may_be_signature_attachment": true, "cid": "fb222496-a1a5-4639-94f2-07b5e35e4068", "inline": false, - "send_as_link": false + "send_as_link": false, + "save_to_entity": true } ] }, @@ -285,6 +289,7 @@ epilot message getMessage -p id=4d74976d-fb64-47fd-85e2-65eea140f5eb --jsonata ' "_updated_at": "2021-02-10T09:14:31.990Z", "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", @@ -338,7 +343,8 @@ epilot message getMessage -p id=4d74976d-fb64-47fd-85e2-65eea140f5eb --jsonata ' "may_be_signature_attachment": true, "cid": "fb222496-a1a5-4639-94f2-07b5e35e4068", "inline": false, - "send_as_link": false + "send_as_link": false, + "save_to_entity": true } ] }, @@ -443,6 +449,24 @@ epilot message searchMessages \ -d '{ "inbox_id": "3f34ce73-089c-4d45-a5ee-c161234e41c3", "q": "subject:\"Request for solar panel price\" AND _tags:INBOX", + "view": { + "folder": "inbox", + "mailbox": "organization", + "labels": ["string"], + "purposes": ["string"], + "filters": ["unread"], + "from": ["string"], + "to": ["string"], + "assigned_to": ["string"], + "include_unassigned": true, + "date_from_days_ago": 0, + "date_to_days_ago": 0, + "email_filter": ["string"], + "text": "string", + "thread_ids": ["string"], + "pinned_by": "string" + }, + "user_groups": ["string"], "fields": ["_id", "_title", "first_name", "account", "!account.*._files", "**._product"], "from": 0, "size": 10, @@ -482,63 +506,16 @@ epilot message searchMessages --jsonata 'results[0]' "_updated_at": "2021-02-10T09:14:31.990Z", "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", - "from": { - "name": "epilot", - "address": "messaging@epilot.cloud", - "email_type": "INTERNAL", - "send_status": "SEND", - "send_error": {} - }, - "reply_to": { - "name": "epilot", - "address": "messaging@epilot.cloud", - "email_type": "INTERNAL", - "send_status": "SEND", - "send_error": {} - }, - "to": [ - { - "name": "epilot", - "address": "messaging@epilot.cloud", - "email_type": "INTERNAL", - "send_status": "SEND", - "send_error": {} - } - ], - "cc": [ - { - "name": "epilot", - "address": "messaging@epilot.cloud", - "email_type": "INTERNAL", - "send_status": "SEND", - "send_error": {} - } - ], - "bcc": [ - { - "name": "epilot", - "address": "messaging@epilot.cloud", - "email_type": "INTERNAL", - "send_status": "SEND", - "send_error": {} - } - ], - "file": { - "$relation": [ - { - "entity_id": "f820ce3b-07b0-45ae-bcc6-babb2f53f79f", - "filename": "Produktinformationen_epilot360_Double_Opt_in.pdf", - "is_message_attachment": true, - "may_be_signature_attachment": true, - "cid": "fb222496-a1a5-4639-94f2-07b5e35e4068", - "inline": false, - "send_as_link": false - } - ] - }, + "from": {}, + "reply_to": {}, + "to": [], + "cc": [], + "bcc": [], + "file": {}, "references": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com> ", "in_reply_to": "", "user_read_message": ["206801", "200109"], @@ -778,6 +755,67 @@ epilot message getUnread -p actor=example --jsonata 'count' --- +### `getUnreadCounts` + +Unread counts for several named scopes in one request. + +`POST /v1/message/unread:counts` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot message getUnreadCounts +``` + +With request body: + +```bash +epilot message getUnreadCounts \ + -d '{ + "actor": "organization", + "email_filter": ["string"], + "user_groups": ["string"], + "scopes": [ + { + "name": "inbox-support", + "type": "organization", + "q": "_tags.keyword:inbox AND !_tags.keyword:trash", + "view_id": "3f34ce73-089c-4d45-a5ee-c161234e41c3", + "inbox_id": "3f34ce73-089c-4d45-a5ee-c161234e41c3" + } + ] +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot message getUnreadCounts +``` + +With JSONata filter: + +```bash +epilot message getUnreadCounts --jsonata 'enabled' +``` + +
+Sample Response + +```json +{ + "enabled": true, + "counts": {}, + "omitted": ["string"] +} +``` + +
+ +--- + ### `markUnreadMessageV2` Mark message as unread within a scope @@ -899,6 +937,24 @@ epilot message searchThreadsV2 \ -d '{ "inbox_id": "3f34ce73-089c-4d45-a5ee-c161234e41c3", "q": "subject:\"Request for solar panel price\" AND _tags:INBOX", + "view": { + "folder": "inbox", + "mailbox": "organization", + "labels": ["string"], + "purposes": ["string"], + "filters": ["unread"], + "from": ["string"], + "to": ["string"], + "assigned_to": ["string"], + "include_unassigned": true, + "date_from_days_ago": 0, + "date_to_days_ago": 0, + "email_filter": ["string"], + "text": "string", + "thread_ids": ["string"], + "pinned_by": "string" + }, + "user_groups": ["string"], "fields": ["_id", "_title", "first_name", "account", "!account.*._files", "**._product"], "from": 0, "size": 10, @@ -962,9 +1018,36 @@ Return all thread id's that match a criteria **Sample Call** +```bash +epilot message searchIds +``` + +With request body: + ```bash epilot message searchIds \ - -d '{"q":"subject:\"Request for solar panel price\" AND _tags:INBOX"}' + -d '{ + "view": { + "folder": "inbox", + "mailbox": "organization", + "labels": ["string"], + "purposes": ["string"], + "filters": ["unread"], + "from": ["string"], + "to": ["string"], + "assigned_to": ["string"], + "include_unassigned": true, + "date_from_days_ago": 0, + "date_to_days_ago": 0, + "email_filter": ["string"], + "text": "string", + "thread_ids": ["string"], + "pinned_by": "string" + }, + "user_groups": ["string"], + "inbox_id": "3f34ce73-089c-4d45-a5ee-c161234e41c3", + "q": "subject:\"Request for solar panel price\" AND _tags:INBOX" +}' ``` Using stdin pipe: @@ -993,6 +1076,51 @@ epilot message searchIds --jsonata 'results[0]' --- +### `getAssigneeWorkload` + +Return the open-thread workload for a set of user ids. + +`POST /v2/message/threads:workload` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot message getAssigneeWorkload \ + -d '{"user_ids":["11000902","11001053"]}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot message getAssigneeWorkload +``` + +With JSONata filter: + +```bash +epilot message getAssigneeWorkload --jsonata 'workloads' +``` + +
+Sample Response + +```json +{ + "workloads": [ + { + "user_id": "11000902", + "open_threads": 2 + } + ] +} +``` + +
+ +--- + ### `updateThread` Modify thread metadata @@ -1030,6 +1158,7 @@ epilot message updateThread --jsonata '$' "latest_message": { "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", @@ -1070,6 +1199,7 @@ epilot message updateThread --jsonata '$' "latest_trash_message": { "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", @@ -1294,12 +1424,24 @@ epilot message getThreadTimeline -p id=123e4567-e89b-12d3-a456-426614174000 --js { "events": [ { + "id": "string", "data": { "type": "THREAD_DONE", "user_id": "123", "organization_id": "456" }, - "timestamp": "2024-01-01T00:00:00Z" + "timestamp": "2024-01-01T00:00:00.000Z", + "message_id": "string", + "source": "user", + "automated": true, + "actor": { + "user_id": "string", + "email": "string" + }, + "automation": { + "id": "string", + "name": "string" + } } ] } @@ -2296,6 +2438,7 @@ epilot message createDraft --jsonata '$' "_updated_at": "2021-02-10T09:14:31.990Z", "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", @@ -2349,7 +2492,8 @@ epilot message createDraft --jsonata '$' "may_be_signature_attachment": true, "cid": "fb222496-a1a5-4639-94f2-07b5e35e4068", "inline": false, - "send_as_link": false + "send_as_link": false, + "save_to_entity": true } ] }, @@ -2399,6 +2543,7 @@ epilot message sendDraft --jsonata '$' "_updated_at": "2021-02-10T09:14:31.990Z", "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", @@ -2452,7 +2597,8 @@ epilot message sendDraft --jsonata '$' "may_be_signature_attachment": true, "cid": "fb222496-a1a5-4639-94f2-07b5e35e4068", "inline": false, - "send_as_link": false + "send_as_link": false, + "save_to_entity": true } ] }, @@ -2517,6 +2663,7 @@ epilot message getMessageV2 -p id=4d74976d-fb64-47fd-85e2-65eea140f5eb --jsonata "_updated_at": "2021-02-10T09:14:31.990Z", "message_id": "<0102017b97a502f8-a67f01c2-68cc-4928-b91b-45853f34e259-000000@eu-west-1.amazonses.com>", "sender": "206801", + "updated_by": "206801", "subject": "Request for solar panel price", "html": "
We at ABC GmbH would like to request a price quote for the solar panel.
", "text": "We at ABC GmbH would like to request a price quote for the solar panel.", @@ -2570,7 +2717,8 @@ epilot message getMessageV2 -p id=4d74976d-fb64-47fd-85e2-65eea140f5eb --jsonata "may_be_signature_attachment": true, "cid": "fb222496-a1a5-4639-94f2-07b5e35e4068", "inline": false, - "send_as_link": false + "send_as_link": false, + "save_to_entity": true } ] }, diff --git a/packages/cli/docs/metering.md b/packages/cli/docs/metering.md index 81ac07f88..7aad0fa87 100644 --- a/packages/cli/docs/metering.md +++ b/packages/cli/docs/metering.md @@ -1,5 +1,6 @@ # Metering API +- **Base URL:** `https://metering.sls.epilot.io` - **API Docs:** [https://docs.epilot.io/api/metering](https://docs.epilot.io/api/metering) The Metering API manages smart meter data, meter counters, and meter readings for epilot customers and administrators. @@ -47,6 +48,7 @@ epilot metering getCustomerMeters - [`createMeterReading`](#createmeterreading) — Inserts a new meter reading. - [`createMeterReadings`](#createmeterreadings) — Inserts multiple meter readings at once. Limited to 100 readings per request. - [`batchWriteMeterReadings`](#batchwritemeterreadings) — Upserts or deletes multiple meter readings at once. Limited to 100 readings per request. +- [`pruneMeterReadings`](#prunemeterreadings) — Deletes every reading of a meter whose `external_id` is NOT in the provided keep list — in a single request. - [`createMeterReadingFromSubmission`](#createmeterreadingfromsubmission) — Creates meter readings from a journey submission payload. - [`getAllowedReadingForMeter`](#getallowedreadingformeter) — Returns the allowed min/max reading range for each counter of the given meter. - [`createReadingWithMeter`](#createreadingwithmeter) — Creates a meter reading along with meter lookup or creation by MA-LO ID and OBIS number. @@ -686,6 +688,9 @@ Inserts multiple meter readings at once. Limited to 100 readings per request. If set to false or not provided, the system performs the following validations: Validation Rule | | `direct` | query | boolean | No | When true, bypasses changeset interception and writes directly to ClickHouse. Used by trusted integrations (e.g., ERP sync) to confirm changes and auto-clear matching pending changesets. | +| `create_ticket` | query | boolean | No | Controls whether a manual-intervention ("Übermittlung Zählerstand") ticket is created for the +written readings. Defaults to true, preserving portal/journey behaviour (readings with +source ECP or journ | **Request Body** (required) @@ -888,6 +893,9 @@ If set to false or not provided, the system performs the following validations: Validation Rule | | `activity_id` | query | string (ulid) | No | Activity to include in event feed | | `direct` | query | boolean | No | When true, bypasses changeset interception and writes directly to ClickHouse. Used by trusted integrations (e.g., ERP sync) to confirm changes and auto-clear matching pending changesets. | +| `create_ticket` | query | boolean | No | Controls whether a manual-intervention ("Übermittlung Zählerstand") ticket is created for the +written readings. Defaults to true, preserving portal/journey behaviour (readings with +source ECP or journ | **Request Body** (required) @@ -973,6 +981,71 @@ epilot metering batchWriteMeterReadings --jsonata 'data' --- +### `pruneMeterReadings` + +Deletes every reading of a meter whose `external_id` is NOT in the provided keep list — in a single request. + +`POST /v2/metering/readings/prune` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `async` | query | boolean | No | Don't wait for the deletions to become visible in GetReadings API. Useful for large prunes | +| `activity_id` | query | string (ulid) | No | Activity to include in event feed | +| `create_ticket` | query | boolean | No | Controls whether a manual-intervention ("Übermittlung Zählerstand") ticket is created for the +written readings. Defaults to true, preserving portal/journey behaviour (readings with +source ECP or journ | +| `dry_run` | query | boolean | No | When true, computes and returns the deleted/kept counts without deleting anything. | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot metering pruneMeterReadings +``` + +With request body: + +```bash +epilot metering pruneMeterReadings \ + -d '{ + "meter_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "counter_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "source": "ECP", + "keep_external_ids": ["erp-reading-1", "erp-reading-2"] +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot metering pruneMeterReadings +``` + +With JSONata filter: + +```bash +epilot metering pruneMeterReadings --jsonata 'data' +``` + +
+Sample Response + +```json +{ + "data": { + "deleted_count": 42, + "kept_count": 12 + } +} +``` + +
+ +--- + ### `createMeterReadingFromSubmission` Creates meter readings from a journey submission payload. diff --git a/packages/cli/docs/notification.md b/packages/cli/docs/notification.md index 9a8fc8ffb..84ce4a9ef 100644 --- a/packages/cli/docs/notification.md +++ b/packages/cli/docs/notification.md @@ -151,7 +151,8 @@ epilot notification getNotificationsV2 --jsonata 'results[0]' "email": false, "in_app": false } - } + }, + "allowed_channels": ["in_app"] } ] } @@ -258,7 +259,8 @@ epilot notification getNotifications --jsonata 'results[0]' "email": false, "in_app": false } - } + }, + "allowed_channels": ["in_app"] } ] } @@ -348,6 +350,7 @@ epilot notification createNotification \ "in_app": false } }, + "allowed_channels": ["in_app"], "read_state": false, "visibility_user_ids": ["1", "2", "3", "4", "5"] }' @@ -465,7 +468,8 @@ epilot notification getNotification -p id=1 --jsonata '$' "email": false, "in_app": false } - } + }, + "allowed_channels": ["in_app"] } ``` diff --git a/packages/cli/docs/permissions.md b/packages/cli/docs/permissions.md index e880b3e92..6ef3d5c7c 100644 --- a/packages/cli/docs/permissions.md +++ b/packages/cli/docs/permissions.md @@ -1,5 +1,6 @@ # Permissions API +- **Base URL:** `https://permissions.sls.epilot.io` - **API Docs:** [https://docs.epilot.io/api/permissions](https://docs.epilot.io/api/permissions) Flexible Role-based Access Control for epilot diff --git a/packages/cli/docs/pricing.md b/packages/cli/docs/pricing.md index b12d828ee..7e5337672 100644 --- a/packages/cli/docs/pricing.md +++ b/packages/cli/docs/pricing.md @@ -75,6 +75,20 @@ epilot pricing $calculatePricingDetails **Product Recommendations API** - [`$productRecommendations`](#$productrecommendations) — Get a list of product recommendations based on the search parameters. +**Conditional Pricing API** +- [`$getConditionSets`](#$getconditionsets) — Returns the condition sets built in for one conditional entity type: the situations a +- [`$resolveConditionalEntity`](#$resolveconditionalentity) — Resolves which of a conditional entity's variants apply to a situation, and returns each one +- [`$createConditionalVariant`](#$createconditionalvariant) — Creates one variant of a conditional entity, together with the first version carrying its +- [`$getActiveConditionalVariantVersion`](#$getactiveconditionalvariantversion) — Returns the version of this variant that is currently in effect — the one with the latest +- [`$replaceActiveConditionalVariantVersion`](#$replaceactiveconditionalvariantversion) — Replaces the values of the version currently in effect, wholesale. +- [`$patchActiveConditionalVariantVersion`](#$patchactiveconditionalvariantversion) — Changes only the fields it names on the version currently in effect. +- [`$deleteConditionalVariant`](#$deleteconditionalvariant) — Removes one variant of a conditional entity: the condition tuple it holds, its registration +- [`$appendConditionalVariantVersion`](#$appendconditionalvariantversion) — Appends a version to a variant: a new set of values taking effect at its own instant. +- [`$getConditionalVariantVersion`](#$getconditionalvariantversion) — Returns one specific version of a variant, by the instant it takes effect — what a form editing +- [`$replaceConditionalVariantVersion`](#$replaceconditionalvariantversion) — Replaces one version's values wholesale, addressed by its `valid_from`. +- [`$patchConditionalVariantVersion`](#$patchconditionalvariantversion) — Changes only the fields it names on one version, addressed by its `valid_from`. +- [`$deleteConditionalVariantVersion`](#$deleteconditionalvariantversion) — Removes one version of a variant. + ### `$calculatePricingDetails` Computes a set of pricing details that can be persisted on an entity with the pricing capability enabled, e.g: Orders or @@ -427,7 +441,7 @@ Update an existing Order | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `id` | path | string | Yes | Order entity ID | +| `id` | path | string (uuid) | Yes | Order entity ID | **Request Body** (required) @@ -2130,3 +2144,978 @@ epilot pricing $productRecommendations --jsonata 'results[0]'
--- + +### `$getConditionSets` + +Returns the condition sets built in for one conditional entity type: the situations a + +`GET /v1/conditional-pricing/{slug}/condition-sets` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type whose built-in condition sets to return | + +**Sample Call** + +```bash +epilot pricing $getConditionSets \ + -p slug=price +``` + +Using positional args for path parameters: + +```bash +epilot pricing $getConditionSets price +``` + +With JSONata filter: + +```bash +epilot pricing $getConditionSets -p slug=price --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "id": "delivery_area", + "label": "Delivery Area", + "description": "string", + "conditions": [ + { + "name": "postal_code", + "label": "Postal Code", + "type": "string", + "options": ["private", "commercial"], + "allow_any": false, + "format": "zipcode" + } + ] + } + ] +} +``` + +
+ +--- + +### `$resolveConditionalEntity` + +Resolves which of a conditional entity's variants apply to a situation, and returns each one + +`POST /v1/conditional-pricing:resolve` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot pricing $resolveConditionalEntity +``` + +With request body: + +```bash +epilot pricing $resolveConditionalEntity \ + -d '{ + "schema": "product", + "entity_id": "price-sp26d1yo", + "context": { + "postal_code": "46045", + "consumption": { + "lt": 5000 + } + }, + "as_of": "2027-03-15T00:00:00Z", + "options": { + "resolve_one": false + } +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot pricing $resolveConditionalEntity +``` + +With JSONata filter: + +```bash +epilot pricing $resolveConditionalEntity --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "_id": "price-sp26d1yo", + "_variant_id": "var-46045", + "_version_valid_from": "2027-01-01T00:00:00.000Z", + "_conditions": { + "postal_code": "46045", + "default": false + } + } + ] +} +``` + +
+ +--- + +### `$createConditionalVariant` + +Creates one variant of a conditional entity, together with the first version carrying its + +`POST /v1/conditional-pricing/{slug}/entities/{entity_id}/variants` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity to add a variant to | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot pricing $createConditionalVariant \ + -p slug=price \ + -p entity_id=price-sp26d1yo +``` + +With request body: + +```bash +epilot pricing $createConditionalVariant \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -d '{ + "conditions": { + "postal_code": "46045" + }, + "default": false, + "valid_from": "2027-01-01T00:00:00Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot pricing $createConditionalVariant price price-sp26d1yo +``` + +Using stdin pipe: + +```bash +cat body.json | epilot pricing $createConditionalVariant -p slug=price -p entity_id=price-sp26d1yo +``` + +With JSONata filter: + +```bash +epilot pricing $createConditionalVariant -p slug=price -p entity_id=price-sp26d1yo --jsonata 'variant_id' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 0, + "warnings": [ + { + "code": "VARIANT_COUNT_APPROACHING_CAP", + "message": "string", + "variant_count": 0, + "cap": 0 + } + ] +} +``` + +
+ +--- + +### `$getActiveConditionalVariantVersion` + +Returns the version of this variant that is currently in effect — the one with the latest + +`GET /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | + +**Sample Call** + +```bash +epilot pricing $getActiveConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 +``` + +Using positional args for path parameters: + +```bash +epilot pricing $getActiveConditionalVariantVersion price price-sp26d1yo var-46045 +``` + +With JSONata filter: + +```bash +epilot pricing $getActiveConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 --jsonata 'variant_id' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 3 +} +``` + +
+ +--- + +### `$replaceActiveConditionalVariantVersion` + +Replaces the values of the version currently in effect, wholesale. + +`PUT /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot pricing $replaceActiveConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 +``` + +With request body: + +```bash +epilot pricing $replaceActiveConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -d '{ + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_revision": 3, + "valid_from": "string", + "conditions": { + "postal_code": "46045" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot pricing $replaceActiveConditionalVariantVersion price price-sp26d1yo var-46045 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot pricing $replaceActiveConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 +``` + +With JSONata filter: + +```bash +epilot pricing $replaceActiveConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 3, + "warnings": [ + { + "code": "ACTIVE_VERSION_REPLACED", + "message": "string", + "valid_from": "2026-08-01T00:00:00.000Z", + "active_valid_from": "2026-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `$patchActiveConditionalVariantVersion` + +Changes only the fields it names on the version currently in effect. + +`PATCH /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot pricing $patchActiveConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 +``` + +With request body: + +```bash +epilot pricing $patchActiveConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -d '{ + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_revision": 3, + "valid_from": "string", + "conditions": { + "postal_code": "46045" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot pricing $patchActiveConditionalVariantVersion price price-sp26d1yo var-46045 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot pricing $patchActiveConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 +``` + +With JSONata filter: + +```bash +epilot pricing $patchActiveConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 3, + "warnings": [ + { + "code": "ACTIVE_VERSION_REPLACED", + "message": "string", + "valid_from": "2026-08-01T00:00:00.000Z", + "active_valid_from": "2026-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `$deleteConditionalVariant` + +Removes one variant of a conditional entity: the condition tuple it holds, its registration + +`DELETE /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant to remove | + +**Sample Call** + +```bash +epilot pricing $deleteConditionalVariant \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 +``` + +Using positional args for path parameters: + +```bash +epilot pricing $deleteConditionalVariant price price-sp26d1yo var-46045 +``` + +With JSONata filter: + +```bash +epilot pricing $deleteConditionalVariant -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 --jsonata 'variant_id' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "tuple_released": true, + "versions_deleted": 0 +} +``` + +
+ +--- + +### `$appendConditionalVariantVersion` + +Appends a version to a variant: a new set of values taking effect at its own instant. + +`POST /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}/versions` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot pricing $appendConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 +``` + +With request body: + +```bash +epilot pricing $appendConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -d '{ + "valid_from": "2027-01-01T00:00:00Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "conditions": { + "postal_code": "46045" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot pricing $appendConditionalVariantVersion price price-sp26d1yo var-46045 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot pricing $appendConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 +``` + +With JSONata filter: + +```bash +epilot pricing $appendConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 3, + "warnings": [ + { + "code": "ACTIVE_VERSION_REPLACED", + "message": "string", + "valid_from": "2026-08-01T00:00:00.000Z", + "active_valid_from": "2026-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `$getConditionalVariantVersion` + +Returns one specific version of a variant, by the instant it takes effect — what a form editing + +`GET /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}/versions/{valid_from}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | +| `valid_from` | path | string | Yes | The version to address, by the instant it takes effect. + +An RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time +(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any acce | + +**Sample Call** + +```bash +epilot pricing $getConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -p valid_from=2027-01-01T00:00:00.000Z +``` + +Using positional args for path parameters: + +```bash +epilot pricing $getConditionalVariantVersion price price-sp26d1yo var-46045 2027-01-01T00:00:00.000Z +``` + +With JSONata filter: + +```bash +epilot pricing $getConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 -p valid_from=2027-01-01T00:00:00.000Z --jsonata 'variant_id' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 3 +} +``` + +
+ +--- + +### `$replaceConditionalVariantVersion` + +Replaces one version's values wholesale, addressed by its `valid_from`. + +`PUT /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}/versions/{valid_from}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | +| `valid_from` | path | string | Yes | The version to address, by the instant it takes effect. + +An RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time +(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any acce | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot pricing $replaceConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -p valid_from=2027-01-01T00:00:00.000Z +``` + +With request body: + +```bash +epilot pricing $replaceConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -p valid_from=2027-01-01T00:00:00.000Z \ + -d '{ + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_revision": 3, + "valid_from": "string", + "conditions": { + "postal_code": "46045" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot pricing $replaceConditionalVariantVersion price price-sp26d1yo var-46045 2027-01-01T00:00:00.000Z +``` + +Using stdin pipe: + +```bash +cat body.json | epilot pricing $replaceConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 -p valid_from=2027-01-01T00:00:00.000Z +``` + +With JSONata filter: + +```bash +epilot pricing $replaceConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 -p valid_from=2027-01-01T00:00:00.000Z --jsonata '$' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 3, + "warnings": [ + { + "code": "ACTIVE_VERSION_REPLACED", + "message": "string", + "valid_from": "2026-08-01T00:00:00.000Z", + "active_valid_from": "2026-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `$patchConditionalVariantVersion` + +Changes only the fields it names on one version, addressed by its `valid_from`. + +`PATCH /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}/versions/{valid_from}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | +| `valid_from` | path | string | Yes | The version to address, by the instant it takes effect. + +An RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time +(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any acce | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot pricing $patchConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -p valid_from=2027-01-01T00:00:00.000Z +``` + +With request body: + +```bash +epilot pricing $patchConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -p valid_from=2027-01-01T00:00:00.000Z \ + -d '{ + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_revision": 3, + "valid_from": "string", + "conditions": { + "postal_code": "46045" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot pricing $patchConditionalVariantVersion price price-sp26d1yo var-46045 2027-01-01T00:00:00.000Z +``` + +Using stdin pipe: + +```bash +cat body.json | epilot pricing $patchConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 -p valid_from=2027-01-01T00:00:00.000Z +``` + +With JSONata filter: + +```bash +epilot pricing $patchConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 -p valid_from=2027-01-01T00:00:00.000Z --jsonata '$' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "conditions": { + "postal_code": "46045", + "default": false + }, + "valid_from": "2027-01-01T00:00:00.000Z", + "values": { + "unit_amount": 2499, + "unit_amount_decimal": "24.99" + }, + "_created_at": "string", + "_updated_at": "string", + "_revision": 3, + "warnings": [ + { + "code": "ACTIVE_VERSION_REPLACED", + "message": "string", + "valid_from": "2026-08-01T00:00:00.000Z", + "active_valid_from": "2026-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + +### `$deleteConditionalVariantVersion` + +Removes one version of a variant. + +`DELETE /v1/conditional-pricing/{slug}/entities/{entity_id}/variants/{variant_id}/versions/{valid_from}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `slug` | path | "product" \| "price" \| "coupon" | Yes | The conditional entity type this variant belongs to | +| `entity_id` | path | string | Yes | The conditional entity the variant belongs to | +| `variant_id` | path | string | Yes | The variant whose timeline this call addresses | +| `valid_from` | path | string | Yes | The version to address, by the instant it takes effect. + +An RFC 3339 date (`2026-01-01`, read as midnight UTC) or date-time +(`2026-01-01T00:00:00Z`), to at most millisecond precision. Written any acce | +| `_revision` | query | number | Yes | The revision marker read from the version being deleted. The delete is refused if the +version has been written since. + +A query parameter rather than a body field, since a DELETE carrying a body travel | + +**Sample Call** + +```bash +epilot pricing $deleteConditionalVariantVersion \ + -p slug=price \ + -p entity_id=price-sp26d1yo \ + -p variant_id=var-46045 \ + -p valid_from=2027-01-01T00:00:00.000Z \ + -p _revision=3 +``` + +Using positional args for path parameters: + +```bash +epilot pricing $deleteConditionalVariantVersion price price-sp26d1yo var-46045 2027-01-01T00:00:00.000Z +``` + +With JSONata filter: + +```bash +epilot pricing $deleteConditionalVariantVersion -p slug=price -p entity_id=price-sp26d1yo -p variant_id=var-46045 -p valid_from=2027-01-01T00:00:00.000Z -p _revision=3 --jsonata 'variant_id' +``` + +
+Sample Response + +```json +{ + "variant_id": "var-46045", + "entity_id": "price-sp26d1yo", + "schema": "product", + "valid_from": "2027-01-01T00:00:00.000Z", + "warnings": [ + { + "code": "ACTIVE_VERSION_REPLACED", + "message": "string", + "valid_from": "2026-08-01T00:00:00.000Z", + "active_valid_from": "2026-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- diff --git a/packages/cli/docs/snapshot.md b/packages/cli/docs/snapshot.md index 854d927ef..934e8e07e 100644 --- a/packages/cli/docs/snapshot.md +++ b/packages/cli/docs/snapshot.md @@ -38,7 +38,7 @@ epilot snapshot listSnapshots **Snapshots** - [`listSnapshots`](#listsnapshots) — List snapshots for the caller's organization, newest first. - [`createSnapshot`](#createsnapshot) — Create a new snapshot of the given resources. Async — returns immediately -- [`captureOrgSnapshot`](#captureorgsnapshot) — Snapshot the caller's whole organization now. Fetches a fresh inventory +- [`captureOrgSnapshot`](#captureorgsnapshot) — Snapshot the caller's whole organization now. Creates a `scope: "org"` - [`getSnapshot`](#getsnapshot) — Fetch a snapshot's metadata. Poll this endpoint to track create/restore progress. - [`deleteSnapshot`](#deletesnapshot) — Delete a snapshot's metadata and S3 manifest. - [`restoreSnapshot`](#restoresnapshot) — Restore a snapshot to the org. Async — returns immediately; client polls @@ -46,6 +46,11 @@ epilot snapshot listSnapshots - [`getSnapshotResource`](#getsnapshotresource) — Fetch one captured resource with its full payload. For UI views - [`listDependencies`](#listdependencies) — Walk the dependency tree for a set of resources and return the full +**ScheduledSnapshots** +- [`getOrgSnapshotSchedule`](#getorgsnapshotschedule) — Return the scheduled-snapshot enrollment config for the caller's org. +- [`putOrgSnapshotSchedule`](#putorgsnapshotschedule) — Create or update the scheduled-snapshot enrollment config for the +- [`deleteOrgSnapshotSchedule`](#deleteorgsnapshotschedule) — Remove the scheduled-snapshot enrollment for the caller's org. + ### `listSnapshots` List snapshots for the caller's organization, newest first. @@ -62,6 +67,9 @@ List snapshots for the caller's organization, newest first. `:`. Split on the first colon — the `` half may contain colons (e.g., role acl ids like `role:acl:internal:foo`). Repeat the | +| `trigger` | query | "manual" \| "sync" \| "blueprint_install" \| "scheduled" | No | Filter to snapshots with a specific trigger. Uses the `byTrigger` GSI +for an efficient indexed query — no table scan. Only snapshots created +after the GSI was added carry this index entry; pre-existin | **Sample Call** @@ -101,13 +109,7 @@ epilot snapshot listSnapshots --jsonata 'results[0]' "name": "string", "user_id": "string", "token_id": "string" - }, - "skipped": [ - { - "lineage_id": "string", - "reason": "modified" - } - ] + } }, "restores": [ { @@ -120,13 +122,7 @@ epilot snapshot listSnapshots --jsonata 'results[0]' "name": "string", "user_id": "string", "token_id": "string" - }, - "skipped": [ - { - "lineage_id": "string", - "reason": "modified" - } - ] + } } ], "matched_count": 0, @@ -195,7 +191,7 @@ epilot snapshot createSnapshot --jsonata '$' ### `captureOrgSnapshot` -Snapshot the caller's whole organization now. Fetches a fresh inventory +Snapshot the caller's whole organization now. Creates a `scope: "org"` `POST /v1/snapshots:capture-org` @@ -275,13 +271,7 @@ epilot snapshot getSnapshot -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata "name": "string", "user_id": "string", "token_id": "string" - }, - "skipped": [ - { - "lineage_id": "string", - "reason": "modified" - } - ] + } }, "restores": [ { @@ -294,13 +284,7 @@ epilot snapshot getSnapshot -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata "name": "string", "user_id": "string", "token_id": "string" - }, - "skipped": [ - { - "lineage_id": "string", - "reason": "modified" - } - ] + } } ], "matched_count": 0, @@ -371,7 +355,7 @@ Restore a snapshot to the org. Async — returns immediately; client polls ```bash epilot snapshot restoreSnapshot \ -p id=123e4567-e89b-12d3-a456-426614174000 \ - -d '{"preserve_modified":false,"preserve_co_owned":false}' + -d '{"exclude_target_ids":["string"]}' ``` Using positional args for path parameters: @@ -495,6 +479,142 @@ epilot snapshot getSnapshotResource -p id=123e4567-e89b-12d3-a456-426614174000 - --- +### `getOrgSnapshotSchedule` + +Return the scheduled-snapshot enrollment config for the caller's org. + +`GET /v1/org-snapshot-schedule` + +**Sample Call** + +```bash +epilot snapshot getOrgSnapshotSchedule +``` + +With JSONata filter: + +```bash +epilot snapshot getOrgSnapshotSchedule --jsonata 'org_id' +``` + +
+Sample Response + +```json +{ + "org_id": "string", + "enabled": true, + "cron_expression": "cron(0 2 * * ? *)", + "timezone": "string", + "retention": { + "value": 1, + "unit": "days" + }, + "excluded_types": ["string"], + "schedule_name": "string", + "last_started_at": "1970-01-01T00:00:00.000Z", + "last_completed_at": "1970-01-01T00:00:00.000Z", + "last_status": "completed", + "created_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `putOrgSnapshotSchedule` + +Create or update the scheduled-snapshot enrollment config for the + +`PUT /v1/org-snapshot-schedule` + +**Request Body** + +**Sample Call** + +```bash +epilot snapshot putOrgSnapshotSchedule +``` + +With request body: + +```bash +epilot snapshot putOrgSnapshotSchedule \ + -d '{ + "enabled": true, + "cron_expression": "cron(0 2 * * ? *)", + "timezone": "Europe/Berlin", + "retention": { + "value": 1, + "unit": "days" + }, + "excluded_types": ["string"] +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot snapshot putOrgSnapshotSchedule +``` + +With JSONata filter: + +```bash +epilot snapshot putOrgSnapshotSchedule --jsonata 'org_id' +``` + +
+Sample Response + +```json +{ + "org_id": "string", + "enabled": true, + "cron_expression": "cron(0 2 * * ? *)", + "timezone": "string", + "retention": { + "value": 1, + "unit": "days" + }, + "excluded_types": ["string"], + "schedule_name": "string", + "last_started_at": "1970-01-01T00:00:00.000Z", + "last_completed_at": "1970-01-01T00:00:00.000Z", + "last_status": "completed", + "created_by": "string", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteOrgSnapshotSchedule` + +Remove the scheduled-snapshot enrollment for the caller's org. + +`DELETE /v1/org-snapshot-schedule` + +**Sample Call** + +```bash +epilot snapshot deleteOrgSnapshotSchedule +``` + +With JSONata filter: + +```bash +epilot snapshot deleteOrgSnapshotSchedule --jsonata '$' +``` + +--- + ### `listDependencies` Walk the dependency tree for a set of resources and return the full diff --git a/packages/cli/docs/targeting.md b/packages/cli/docs/targeting.md index b1ed0c0db..26159f92b 100644 --- a/packages/cli/docs/targeting.md +++ b/packages/cli/docs/targeting.md @@ -41,6 +41,7 @@ epilot targeting changeCampaignStatus -p campaign_id=b8c01433-5556-4e2b-aad4-6f5 - [`getCampaignPortals`](#getcampaignportals) — Get the list of portals and its widgets where the campaign is used. - [`setupCampaign`](#setupcampaign) — Creates a `campaign` entity together with its related entities and configurations in a single call. - [`matchCampaigns`](#matchcampaigns) — Match campaigns based on target entities. +- [`discoverCampaigns`](#discovercampaigns) — Given an entity, returns the Next Best Actions it should see on the Entity-UI channel. **Campaign Delivery** - [`retriggerCampaignAutomations`](#retriggercampaignautomations) — Retrigger automation executions for specific campaign recipients that have failed. @@ -53,7 +54,10 @@ epilot targeting changeCampaignStatus -p campaign_id=b8c01433-5556-4e2b-aad4-6f5 - [`createRecipient`](#createrecipient) — Creates a new recipient associated with a campaign. - [`updateRecipient`](#updaterecipient) — Updates a recipient's attributes. - [`updateRecipientPortalStatus`](#updaterecipientportalstatus) — Updates the portal status for a specific campaign recipient. +- [`updateRecipientEntityUiStatus`](#updaterecipiententityuistatus) — Records a Next Best Action interaction for a recipient on the Entity-UI channel. +- [`restoreRecipientEntityUiStatus`](#restorerecipiententityuistatus) — Reverses a dismissal so the Next Best Action becomes visible to the entity again. This is - [`getRecipients`](#getrecipients) — Get a paginated list of recipients for a campaign. +- [`getEmailStats`](#getemailstats) — Aggregate email delivery counts for a campaign, for the KPI summary on the campaign UI. ### `changeCampaignStatus` @@ -483,6 +487,102 @@ epilot targeting matchCampaigns --jsonata 'results[0]' --- +### `discoverCampaigns` + +Given an entity, returns the Next Best Actions it should see on the Entity-UI channel. + +`POST /v1/campaign:discover` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot targeting discoverCampaigns \ + -d '{"entity_id":"b8c01433-5556-4e2b-aad4-6f5348d1df84","entity_schema":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot targeting discoverCampaigns +``` + +With JSONata filter: + +```bash +epilot targeting discoverCampaigns --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "hits": 0, + "results": [ + { + "campaign_id": "b8c01433-5556-4e2b-aad4-6f5348d1df84", + "nba": { + "category": "string", + "icon": { + "name": "string", + "color": "string" + }, + "title": "string", + "body": "string", + "priority": "medium", + "is_dismissable": true, + "cta": { + "type": "journey", + "target": "string", + "context_params": [ + { + "key": "string", + "value": "string" + } + ] + } + }, + "status": "seen", + "status_updated_at": "1970-01-01T00:00:00.000Z" + } + ], + "dismissed": [ + { + "campaign_id": "b8c01433-5556-4e2b-aad4-6f5348d1df84", + "nba": { + "category": "string", + "icon": { + "name": "string", + "color": "string" + }, + "title": "string", + "body": "string", + "priority": "medium", + "is_dismissable": true, + "cta": { + "type": "journey", + "target": "string", + "context_params": [ + { + "key": "string", + "value": "string" + } + ] + } + }, + "status": "seen", + "status_updated_at": "1970-01-01T00:00:00.000Z" + } + ] +} +``` + +
+ +--- + ### `matchTargets` Find targets from the provided list that include the provide entities. @@ -676,6 +776,29 @@ epilot targeting createRecipient -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1 "portal_status": "sent", "portal_status_updated_at": "1970-01-01T00:00:00.000Z", "portal_state": {}, + "entity_ui_status": "seen", + "entity_ui_status_updated_at": "1970-01-01T00:00:00.000Z", + "entity_ui_status_before_dismiss": "seen", + "message_entity_id": "string", + "email_status": "sent", + "email_status_updated_at": "1970-01-01T00:00:00.000Z", + "email_bounce_type": "permanent", + "email_bounce_subtype": "string", + "email_complaint_type": "string", + "email_bounce_reason": "string", + "email_send_error": {}, + "email_unsubscribed_at": "1970-01-01T00:00:00.000Z", + "email_open_count": 0, + "email_first_opened_at": "1970-01-01T00:00:00.000Z", + "email_last_opened_at": "1970-01-01T00:00:00.000Z", + "email_click_count": 0, + "email_first_clicked_at": "1970-01-01T00:00:00.000Z", + "email_clicked_links": [ + { + "url": "string", + "clicked_at": "1970-01-01T00:00:00.000Z" + } + ], "updated_at": "1970-01-01T00:00:00.000Z" } ``` @@ -739,6 +862,29 @@ epilot targeting updateRecipient -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1 "portal_status": "sent", "portal_status_updated_at": "1970-01-01T00:00:00.000Z", "portal_state": {}, + "entity_ui_status": "seen", + "entity_ui_status_updated_at": "1970-01-01T00:00:00.000Z", + "entity_ui_status_before_dismiss": "seen", + "message_entity_id": "string", + "email_status": "sent", + "email_status_updated_at": "1970-01-01T00:00:00.000Z", + "email_bounce_type": "permanent", + "email_bounce_subtype": "string", + "email_complaint_type": "string", + "email_bounce_reason": "string", + "email_send_error": {}, + "email_unsubscribed_at": "1970-01-01T00:00:00.000Z", + "email_open_count": 0, + "email_first_opened_at": "1970-01-01T00:00:00.000Z", + "email_last_opened_at": "1970-01-01T00:00:00.000Z", + "email_click_count": 0, + "email_first_clicked_at": "1970-01-01T00:00:00.000Z", + "email_clicked_links": [ + { + "url": "string", + "clicked_at": "1970-01-01T00:00:00.000Z" + } + ], "updated_at": "1970-01-01T00:00:00.000Z" } ``` @@ -802,6 +948,192 @@ epilot targeting updateRecipientPortalStatus -p campaign_id=b8c01433-5556-4e2b-a "portal_status": "sent", "portal_status_updated_at": "1970-01-01T00:00:00.000Z", "portal_state": {}, + "entity_ui_status": "seen", + "entity_ui_status_updated_at": "1970-01-01T00:00:00.000Z", + "entity_ui_status_before_dismiss": "seen", + "message_entity_id": "string", + "email_status": "sent", + "email_status_updated_at": "1970-01-01T00:00:00.000Z", + "email_bounce_type": "permanent", + "email_bounce_subtype": "string", + "email_complaint_type": "string", + "email_bounce_reason": "string", + "email_send_error": {}, + "email_unsubscribed_at": "1970-01-01T00:00:00.000Z", + "email_open_count": 0, + "email_first_opened_at": "1970-01-01T00:00:00.000Z", + "email_last_opened_at": "1970-01-01T00:00:00.000Z", + "email_click_count": 0, + "email_first_clicked_at": "1970-01-01T00:00:00.000Z", + "email_clicked_links": [ + { + "url": "string", + "clicked_at": "1970-01-01T00:00:00.000Z" + } + ], + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `updateRecipientEntityUiStatus` + +Records a Next Best Action interaction for a recipient on the Entity-UI channel. + +`PATCH /v1/campaign/{campaign_id}/recipient/{recipient_id}/entity_ui:status` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `campaign_id` | path | string (uuid) | Yes | The campaign ID | +| `recipient_id` | path | string (uuid) | Yes | The entity ID of the recipient | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot targeting updateRecipientEntityUiStatus \ + -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 \ + -p recipient_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 \ + -d '{"status":"seen","entity_schema":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot targeting updateRecipientEntityUiStatus b8c01433-5556-4e2b-aad4-6f5348d1df84 b8c01433-5556-4e2b-aad4-6f5348d1df84 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot targeting updateRecipientEntityUiStatus -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 -p recipient_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 +``` + +With JSONata filter: + +```bash +epilot targeting updateRecipientEntityUiStatus -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 -p recipient_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 --jsonata 'entity_id' +``` + +
+Sample Response + +```json +{ + "entity_id": "b8c01433-5556-4e2b-aad4-6f5348d1df84", + "entity_schema": "string", + "title": "string", + "automation_status": "pending", + "automation_execution_id": "string", + "portal_status": "sent", + "portal_status_updated_at": "1970-01-01T00:00:00.000Z", + "portal_state": {}, + "entity_ui_status": "seen", + "entity_ui_status_updated_at": "1970-01-01T00:00:00.000Z", + "entity_ui_status_before_dismiss": "seen", + "message_entity_id": "string", + "email_status": "sent", + "email_status_updated_at": "1970-01-01T00:00:00.000Z", + "email_bounce_type": "permanent", + "email_bounce_subtype": "string", + "email_complaint_type": "string", + "email_bounce_reason": "string", + "email_send_error": {}, + "email_unsubscribed_at": "1970-01-01T00:00:00.000Z", + "email_open_count": 0, + "email_first_opened_at": "1970-01-01T00:00:00.000Z", + "email_last_opened_at": "1970-01-01T00:00:00.000Z", + "email_click_count": 0, + "email_first_clicked_at": "1970-01-01T00:00:00.000Z", + "email_clicked_links": [ + { + "url": "string", + "clicked_at": "1970-01-01T00:00:00.000Z" + } + ], + "updated_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `restoreRecipientEntityUiStatus` + +Reverses a dismissal so the Next Best Action becomes visible to the entity again. This is + +`POST /v1/campaign/{campaign_id}/recipient/{recipient_id}/entity_ui:restore` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `campaign_id` | path | string (uuid) | Yes | The campaign ID | +| `recipient_id` | path | string (uuid) | Yes | The entity ID of the recipient | + +**Sample Call** + +```bash +epilot targeting restoreRecipientEntityUiStatus \ + -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 \ + -p recipient_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 +``` + +Using positional args for path parameters: + +```bash +epilot targeting restoreRecipientEntityUiStatus b8c01433-5556-4e2b-aad4-6f5348d1df84 b8c01433-5556-4e2b-aad4-6f5348d1df84 +``` + +With JSONata filter: + +```bash +epilot targeting restoreRecipientEntityUiStatus -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 -p recipient_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 --jsonata 'entity_id' +``` + +
+Sample Response + +```json +{ + "entity_id": "b8c01433-5556-4e2b-aad4-6f5348d1df84", + "entity_schema": "string", + "title": "string", + "automation_status": "pending", + "automation_execution_id": "string", + "portal_status": "sent", + "portal_status_updated_at": "1970-01-01T00:00:00.000Z", + "portal_state": {}, + "entity_ui_status": "seen", + "entity_ui_status_updated_at": "1970-01-01T00:00:00.000Z", + "entity_ui_status_before_dismiss": "seen", + "message_entity_id": "string", + "email_status": "sent", + "email_status_updated_at": "1970-01-01T00:00:00.000Z", + "email_bounce_type": "permanent", + "email_bounce_subtype": "string", + "email_complaint_type": "string", + "email_bounce_reason": "string", + "email_send_error": {}, + "email_unsubscribed_at": "1970-01-01T00:00:00.000Z", + "email_open_count": 0, + "email_first_opened_at": "1970-01-01T00:00:00.000Z", + "email_last_opened_at": "1970-01-01T00:00:00.000Z", + "email_click_count": 0, + "email_first_clicked_at": "1970-01-01T00:00:00.000Z", + "email_clicked_links": [ + { + "url": "string", + "clicked_at": "1970-01-01T00:00:00.000Z" + } + ], "updated_at": "1970-01-01T00:00:00.000Z" } ``` @@ -824,8 +1156,12 @@ Get a paginated list of recipients for a campaign. | `limit` | query | number | No | Number of items to return | | `next` | query | string | No | Cursor for pagination | | `q` | query | string | No | Search by recipient title | -| `automation_status` | query | "pending" \| "in_progress" \| "success" \| "failed" \| "cancelled" | No | Filter by automation status | +| `automation_status` | query | "pending" \| "in_progress" \| "success" \| "failed" \| "cancelled"[] | No | Filter by automation status. Repeat the parameter to match multiple +statuses in one request +(e.g. automation_status=failed&automation_status=cancelled). + | | `portal_status` | query | "sent" \| "seen" \| "dismissed" \| "clicked" | No | Filter by portal status | +| `email_status` | query | "sent" \| "delivered" \| "bounced" \| "complained" \| "failed" | No | Filter by email delivery status (e.g. bounced, delivered). The response total reflects the count for the filtered status. | **Sample Call** @@ -861,6 +1197,29 @@ epilot targeting getRecipients -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df "portal_status": "sent", "portal_status_updated_at": "1970-01-01T00:00:00.000Z", "portal_state": {}, + "entity_ui_status": "seen", + "entity_ui_status_updated_at": "1970-01-01T00:00:00.000Z", + "entity_ui_status_before_dismiss": "seen", + "message_entity_id": "string", + "email_status": "sent", + "email_status_updated_at": "1970-01-01T00:00:00.000Z", + "email_bounce_type": "permanent", + "email_bounce_subtype": "string", + "email_complaint_type": "string", + "email_bounce_reason": "string", + "email_send_error": {}, + "email_unsubscribed_at": "1970-01-01T00:00:00.000Z", + "email_open_count": 0, + "email_first_opened_at": "1970-01-01T00:00:00.000Z", + "email_last_opened_at": "1970-01-01T00:00:00.000Z", + "email_click_count": 0, + "email_first_clicked_at": "1970-01-01T00:00:00.000Z", + "email_clicked_links": [ + { + "url": "string", + "clicked_at": "1970-01-01T00:00:00.000Z" + } + ], "updated_at": "1970-01-01T00:00:00.000Z" } ], @@ -872,3 +1231,59 @@ epilot targeting getRecipients -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df
--- + +### `getEmailStats` + +Aggregate email delivery counts for a campaign, for the KPI summary on the campaign UI. + +`GET /v1/campaign/{campaign_id}/email-stats` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `campaign_id` | path | string (uuid) | Yes | The campaign ID | + +**Sample Call** + +```bash +epilot targeting getEmailStats \ + -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 +``` + +Using positional args for path parameters: + +```bash +epilot targeting getEmailStats b8c01433-5556-4e2b-aad4-6f5348d1df84 +``` + +With JSONata filter: + +```bash +epilot targeting getEmailStats -p campaign_id=b8c01433-5556-4e2b-aad4-6f5348d1df84 --jsonata 'total_emailed' +``` + +
+Sample Response + +```json +{ + "total_emailed": 0, + "sent": 0, + "delivered": 0, + "bounced": 0, + "bounced_hard": 0, + "bounced_soft": 0, + "complained": 0, + "failed": 0, + "unsubscribed": 0, + "opened": 0, + "clicked": 0, + "total_opens": 0, + "total_clicks": 0 +} +``` + +
+ +--- diff --git a/packages/cli/docs/user.md b/packages/cli/docs/user.md index 087b4655f..01d1d3214 100644 --- a/packages/cli/docs/user.md +++ b/packages/cli/docs/user.md @@ -53,6 +53,7 @@ epilot user signUpUser - [`getGroupsForUser`](#getgroupsforuser) — Get groups of a user - [`verifyEmailWithToken`](#verifyemailwithtoken) — Update new email using an verification token - [`requestPasswordReset`](#requestpasswordreset) — Request a password reset email for the given email address. Always +- [`resetPassword`](#resetpassword) — Set a new password using a reset token from the password reset email. - [`checkInviteToken`](#checkinvitetoken) — Check an invite token - [`activateUser`](#activateuser) — Activate user using an invite token - [`rejectInvite`](#rejectinvite) — Reject an invite @@ -230,6 +231,8 @@ epilot user getMeV2 --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -271,12 +274,19 @@ epilot user getMeV2 --jsonata 'id' "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ { "name": "profileImageName", "value": "avatar.png" } - ] + ], + "tags": ["non-billable"] } ``` @@ -555,6 +565,8 @@ epilot user listUsersV2 --jsonata 'results[0]' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -596,12 +608,19 @@ epilot user listUsersV2 --jsonata 'results[0]' "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ { "name": "profileImageName", "value": "avatar.png" } - ] + ], + "tags": ["non-billable"] } ] } @@ -656,6 +675,8 @@ epilot user getUserV2 -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -697,12 +718,19 @@ epilot user getUserV2 -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ { "name": "profileImageName", "value": "avatar.png" } - ] + ], + "tags": ["non-billable"] } ``` @@ -746,6 +774,8 @@ epilot user updateUserV2 \ "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -787,12 +817,19 @@ epilot user updateUserV2 \ "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ { "name": "profileImageName", "value": "avatar.png" } - ] + ], + "tags": ["non-billable"] }' ``` @@ -828,6 +865,8 @@ epilot user updateUserV2 -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'i "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -869,12 +908,19 @@ epilot user updateUserV2 -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'i "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ { "name": "profileImageName", "value": "avatar.png" } - ] + ], + "tags": ["non-billable"] } ``` @@ -985,6 +1031,8 @@ epilot user inviteUser --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1026,12 +1074,19 @@ epilot user inviteUser --jsonata 'id' "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ { "name": "profileImageName", "value": "avatar.png" } - ] + ], + "tags": ["non-billable"] } ``` @@ -1080,6 +1135,8 @@ epilot user resendUserInvitation --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1121,12 +1178,19 @@ epilot user resendUserInvitation --jsonata 'id' "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ { "name": "profileImageName", "value": "avatar.png" } - ] + ], + "tags": ["non-billable"] } ``` @@ -1227,6 +1291,8 @@ epilot user getGroupsForUser -p id=123e4567-e89b-12d3-a456-426614174000 --jsonat "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1244,7 +1310,9 @@ epilot user getGroupsForUser -p id=123e4567-e89b-12d3-a456-426614174000 --jsonat "image_uri": {}, "favorites": {}, "email_notification_setting": {}, + "in_app_notification_setting": {}, "properties": [], + "tags": ["non-billable"], "crt_index": 3 }, "users": [ @@ -1364,6 +1432,8 @@ epilot user createGroup --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1402,9 +1472,16 @@ epilot user createGroup --jsonata 'id' "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ {} ], + "tags": ["non-billable"], "crt_index": 3 }, "users": [ @@ -1418,6 +1495,8 @@ epilot user createGroup --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1435,7 +1514,9 @@ epilot user createGroup --jsonata 'id' "image_uri": {}, "favorites": {}, "email_notification_setting": {}, - "properties": [] + "in_app_notification_setting": {}, + "properties": [], + "tags": ["non-billable"] } ], "image_uri": { @@ -1502,6 +1583,8 @@ epilot user getGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1540,9 +1623,16 @@ epilot user getGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ {} ], + "tags": ["non-billable"], "crt_index": 3 }, "users": [ @@ -1556,6 +1646,8 @@ epilot user getGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1573,7 +1665,9 @@ epilot user getGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id' "image_uri": {}, "favorites": {}, "email_notification_setting": {}, - "properties": [] + "in_app_notification_setting": {}, + "properties": [], + "tags": ["non-billable"] } ], "image_uri": { @@ -1648,6 +1742,8 @@ epilot user updateGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1686,9 +1782,16 @@ epilot user updateGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ {} ], + "tags": ["non-billable"], "crt_index": 3 }, "users": [ @@ -1702,6 +1805,8 @@ epilot user updateGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1719,7 +1824,9 @@ epilot user updateGroup -p id=123e4567-e89b-12d3-a456-426614174000 --jsonata 'id "image_uri": {}, "favorites": {}, "email_notification_setting": {}, - "properties": [] + "in_app_notification_setting": {}, + "properties": [], + "tags": ["non-billable"] } ], "image_uri": { @@ -1818,6 +1925,8 @@ epilot user advanceUserAssignment -p id=123e4567-e89b-12d3-a456-426614174000 --j "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1856,9 +1965,16 @@ epilot user advanceUserAssignment -p id=123e4567-e89b-12d3-a456-426614174000 --j "created_opportunity_auto": true, "deleted_opportunity": true }, + "in_app_notification_setting": { + "integration_critical_error": true, + "integration_error_threshold": true, + "assigned_opportunity": true, + "assigned_task": true + }, "properties": [ {} ], + "tags": ["non-billable"], "crt_index": 3 }, "users": [ @@ -1872,6 +1988,8 @@ epilot user advanceUserAssignment -p id=123e4567-e89b-12d3-a456-426614174000 --j "email": "user@example.com", "draft_email": "user@example.com", "draft_email_expires_at": "1970-01-01T00:00:00.000Z", + "password_reset_token_hash": "string", + "password_reset_token_expires_at": "1970-01-01T00:00:00.000Z", "department": "Sales", "phone": 1234567890, "secondary_phone": 1234567890, @@ -1889,7 +2007,9 @@ epilot user advanceUserAssignment -p id=123e4567-e89b-12d3-a456-426614174000 --j "image_uri": {}, "favorites": {}, "email_notification_setting": {}, - "properties": [] + "in_app_notification_setting": {}, + "properties": [], + "tags": ["non-billable"] } ], "image_uri": { @@ -2151,6 +2271,46 @@ epilot user requestPasswordReset --jsonata 'message' --- +### `resetPassword` + +Set a new password using a reset token from the password reset email. + +`POST /v2/users/public/resetPassword` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot user resetPassword \ + -d '{"email":"test@example.com","token":"string","password":"string"}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot user resetPassword +``` + +With JSONata filter: + +```bash +epilot user resetPassword --jsonata 'success' +``` + +
+Sample Response + +```json +{ + "success": true +} +``` + +
+ +--- + ### `checkInviteToken` Check an invite token @@ -2316,7 +2476,8 @@ epilot user getUserLoginParametersV2 -p username=example --jsonata 'login_parame "cognito_oauth_domain": "epilot-org-123", "cognito_oauth_scopes": ["openid"], "oauth_response_type": "code", - "passkey_enabled": true + "passkey_enabled": true, + "passkeys_registered": true } ] } @@ -2446,7 +2607,8 @@ epilot user resolveDiscoverableCredential --jsonata 'email' "cognito_oauth_domain": "epilot-org-123", "cognito_oauth_scopes": ["openid"], "oauth_response_type": "code", - "passkey_enabled": true + "passkey_enabled": true, + "passkeys_registered": true } ] } @@ -2874,7 +3036,8 @@ epilot user getUserLoginParameters -p username=example --jsonata 'login_paramete "cognito_oauth_domain": "epilot-org-123", "cognito_oauth_scopes": ["openid"], "oauth_response_type": "code", - "passkey_enabled": true + "passkey_enabled": true, + "passkeys_registered": true } ] } diff --git a/packages/cli/docs/validation-rules.md b/packages/cli/docs/validation-rules.md index aed974980..b041c9b24 100644 --- a/packages/cli/docs/validation-rules.md +++ b/packages/cli/docs/validation-rules.md @@ -3,6 +3,8 @@ - **Base URL:** `https://validation-rules.sls.epilot.io` - **API Docs:** [https://docs.epilot.io/api/validation-rules](https://docs.epilot.io/api/validation-rules) +The Validation Rules API manages reusable input validation rules for epilot journeys and entity attributes. + ## Quick Start ```bash @@ -34,17 +36,17 @@ epilot validation-rules getValidationRules ## Operations **Validation Rules** -- [`getValidationRules`](#getvalidationrules) — Gets all validation rules by organization Id -- [`createValidationRule`](#createvalidationrule) — Creates a new validation rule -- [`getValidationRuleById`](#getvalidationrulebyid) — Retrieves a specific validation rule by its ID -- [`updateValidationRule`](#updatevalidationrule) — Updates an existing validation rule partially by ID -- [`deleteValidationRule`](#deletevalidationrule) — Deletes a validation rule by ID -- [`addUsedByReference`](#addusedbyreference) — Adds a single reference to the usedBy array of a validation rule -- [`removeUsedByReference`](#removeusedbyreference) — Removes a specific reference from the usedBy array of a validation rule +- [`getValidationRules`](#getvalidationrules) — Returns all validation rules belonging to the authenticated user's organization. +- [`createValidationRule`](#createvalidationrule) — Creates a new validation rule for the authenticated organization. +- [`getValidationRuleById`](#getvalidationrulebyid) — Retrieves a specific validation rule by its unique ID. +- [`updateValidationRule`](#updatevalidationrule) — Partially updates an existing validation rule by ID. Only the fields provided in the request body are updated. +- [`deleteValidationRule`](#deletevalidationrule) — Permanently deletes a validation rule by ID. Any journeys or entity attributes referencing this rule should be updated b +- [`addUsedByReference`](#addusedbyreference) — Adds a single `used_by` reference to an existing validation rule. +- [`removeUsedByReference`](#removeusedbyreference) — Removes a specific `used_by` reference from an existing validation rule. ### `getValidationRules` -Gets all validation rules by organization Id +Returns all validation rules belonging to the authenticated user's organization. `GET /v1/validation-rules` @@ -72,8 +74,7 @@ epilot validation-rules getValidationRules --jsonata 'results[0]' "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -103,6 +104,11 @@ epilot validation-rules getValidationRules --jsonata 'results[0]' ] } }, + "contexts": [ + { + "schema": "contract" + } + ], "_schema_version": "string", "_id": "string", "_organization_id": "string", @@ -121,11 +127,11 @@ epilot validation-rules getValidationRules --jsonata 'results[0]' ### `createValidationRule` -Creates a new validation rule +Creates a new validation rule for the authenticated organization. `POST /v1/validation-rules` -**Request Body** +**Request Body** (required) **Sample Call** @@ -143,8 +149,7 @@ epilot validation-rules createValidationRule \ "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -173,7 +178,12 @@ epilot validation-rules createValidationRule \ } ] } - } + }, + "contexts": [ + { + "schema": "contract" + } + ] }' ``` @@ -199,8 +209,7 @@ epilot validation-rules createValidationRule --jsonata '$' "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -230,6 +239,11 @@ epilot validation-rules createValidationRule --jsonata '$' ] } }, + "contexts": [ + { + "schema": "contract" + } + ], "_schema_version": "string", "_id": "string", "_organization_id": "string", @@ -246,7 +260,7 @@ epilot validation-rules createValidationRule --jsonata '$' ### `getValidationRuleById` -Retrieves a specific validation rule by its ID +Retrieves a specific validation rule by its unique ID. `GET /v1/validation-rules/{ruleId}` @@ -260,19 +274,19 @@ Retrieves a specific validation rule by its ID ```bash epilot validation-rules getValidationRuleById \ - -p ruleId=123e4567-e89b-12d3-a456-426614174000 + -p ruleId=rule-abc123 ``` Using positional args for path parameters: ```bash -epilot validation-rules getValidationRuleById 123e4567-e89b-12d3-a456-426614174000 +epilot validation-rules getValidationRuleById rule-abc123 ``` With JSONata filter: ```bash -epilot validation-rules getValidationRuleById -p ruleId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot validation-rules getValidationRuleById -p ruleId=rule-abc123 --jsonata '$' ```
@@ -285,8 +299,7 @@ epilot validation-rules getValidationRuleById -p ruleId=123e4567-e89b-12d3-a456- "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -316,6 +329,11 @@ epilot validation-rules getValidationRuleById -p ruleId=123e4567-e89b-12d3-a456- ] } }, + "contexts": [ + { + "schema": "contract" + } + ], "_schema_version": "string", "_id": "string", "_organization_id": "string", @@ -332,7 +350,7 @@ epilot validation-rules getValidationRuleById -p ruleId=123e4567-e89b-12d3-a456- ### `updateValidationRule` -Updates an existing validation rule partially by ID +Partially updates an existing validation rule by ID. Only the fields provided in the request body are updated. `PATCH /v1/validation-rules/{ruleId}` @@ -348,22 +366,21 @@ Updates an existing validation rule partially by ID ```bash epilot validation-rules updateValidationRule \ - -p ruleId=123e4567-e89b-12d3-a456-426614174000 + -p ruleId=rule-abc123 ``` With request body: ```bash epilot validation-rules updateValidationRule \ - -p ruleId=123e4567-e89b-12d3-a456-426614174000 \ + -p ruleId=rule-abc123 \ -d '{ "title": "string", "placeholder": "string", "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -392,26 +409,31 @@ epilot validation-rules updateValidationRule \ } ] } - } + }, + "contexts": [ + { + "schema": "contract" + } + ] }' ``` Using positional args for path parameters: ```bash -epilot validation-rules updateValidationRule 123e4567-e89b-12d3-a456-426614174000 +epilot validation-rules updateValidationRule rule-abc123 ``` Using stdin pipe: ```bash -cat body.json | epilot validation-rules updateValidationRule -p ruleId=123e4567-e89b-12d3-a456-426614174000 +cat body.json | epilot validation-rules updateValidationRule -p ruleId=rule-abc123 ``` With JSONata filter: ```bash -epilot validation-rules updateValidationRule -p ruleId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot validation-rules updateValidationRule -p ruleId=rule-abc123 --jsonata '$' ```
@@ -424,8 +446,7 @@ epilot validation-rules updateValidationRule -p ruleId=123e4567-e89b-12d3-a456-4 "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -455,6 +476,11 @@ epilot validation-rules updateValidationRule -p ruleId=123e4567-e89b-12d3-a456-4 ] } }, + "contexts": [ + { + "schema": "contract" + } + ], "_schema_version": "string", "_id": "string", "_organization_id": "string", @@ -471,7 +497,7 @@ epilot validation-rules updateValidationRule -p ruleId=123e4567-e89b-12d3-a456-4 ### `deleteValidationRule` -Deletes a validation rule by ID +Permanently deletes a validation rule by ID. Any journeys or entity attributes referencing this rule should be updated b `DELETE /v1/validation-rules/{ruleId}` @@ -485,26 +511,26 @@ Deletes a validation rule by ID ```bash epilot validation-rules deleteValidationRule \ - -p ruleId=123e4567-e89b-12d3-a456-426614174000 + -p ruleId=rule-abc123 ``` Using positional args for path parameters: ```bash -epilot validation-rules deleteValidationRule 123e4567-e89b-12d3-a456-426614174000 +epilot validation-rules deleteValidationRule rule-abc123 ``` With JSONata filter: ```bash -epilot validation-rules deleteValidationRule -p ruleId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot validation-rules deleteValidationRule -p ruleId=rule-abc123 --jsonata '$' ``` --- ### `addUsedByReference` -Adds a single reference to the usedBy array of a validation rule +Adds a single `used_by` reference to an existing validation rule. `POST /v1/validation-rules/{ruleId}/used-by` @@ -520,26 +546,26 @@ Adds a single reference to the usedBy array of a validation rule ```bash epilot validation-rules addUsedByReference \ - -p ruleId=123e4567-e89b-12d3-a456-426614174000 \ - -d '{"type":"journey","schema_slug":"string","source_id":"string"}' + -p ruleId=rule-abc123 \ + -d '{"type":"journey","source_id":"journey-xyz789"}' ``` Using positional args for path parameters: ```bash -epilot validation-rules addUsedByReference 123e4567-e89b-12d3-a456-426614174000 +epilot validation-rules addUsedByReference rule-abc123 ``` Using stdin pipe: ```bash -cat body.json | epilot validation-rules addUsedByReference -p ruleId=123e4567-e89b-12d3-a456-426614174000 +cat body.json | epilot validation-rules addUsedByReference -p ruleId=rule-abc123 ``` With JSONata filter: ```bash -epilot validation-rules addUsedByReference -p ruleId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot validation-rules addUsedByReference -p ruleId=rule-abc123 --jsonata '$' ```
@@ -552,8 +578,7 @@ epilot validation-rules addUsedByReference -p ruleId=123e4567-e89b-12d3-a456-426 "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -583,6 +608,11 @@ epilot validation-rules addUsedByReference -p ruleId=123e4567-e89b-12d3-a456-426 ] } }, + "contexts": [ + { + "schema": "contract" + } + ], "_schema_version": "string", "_id": "string", "_organization_id": "string", @@ -599,7 +629,7 @@ epilot validation-rules addUsedByReference -p ruleId=123e4567-e89b-12d3-a456-426 ### `removeUsedByReference` -Removes a specific reference from the usedBy array of a validation rule +Removes a specific `used_by` reference from an existing validation rule. `DELETE /v1/validation-rules/{ruleId}/used-by` @@ -615,26 +645,26 @@ Removes a specific reference from the usedBy array of a validation rule ```bash epilot validation-rules removeUsedByReference \ - -p ruleId=123e4567-e89b-12d3-a456-426614174000 \ - -d '{"type":"journey","schema_slug":"string","source_id":"string"}' + -p ruleId=rule-abc123 \ + -d '{"type":"journey","source_id":"journey-xyz789"}' ``` Using positional args for path parameters: ```bash -epilot validation-rules removeUsedByReference 123e4567-e89b-12d3-a456-426614174000 +epilot validation-rules removeUsedByReference rule-abc123 ``` Using stdin pipe: ```bash -cat body.json | epilot validation-rules removeUsedByReference -p ruleId=123e4567-e89b-12d3-a456-426614174000 +cat body.json | epilot validation-rules removeUsedByReference -p ruleId=rule-abc123 ``` With JSONata filter: ```bash -epilot validation-rules removeUsedByReference -p ruleId=123e4567-e89b-12d3-a456-426614174000 --jsonata '$' +epilot validation-rules removeUsedByReference -p ruleId=rule-abc123 --jsonata '$' ```
@@ -647,8 +677,7 @@ epilot validation-rules removeUsedByReference -p ruleId=123e4567-e89b-12d3-a456- "used_by": [ { "type": "journey", - "schema_slug": "string", - "source_id": "string" + "source_id": "journey-xyz789" } ], "rule": { @@ -678,6 +707,11 @@ epilot validation-rules removeUsedByReference -p ruleId=123e4567-e89b-12d3-a456- ] } }, + "contexts": [ + { + "schema": "contract" + } + ], "_schema_version": "string", "_id": "string", "_organization_id": "string", diff --git a/packages/cli/docs/webhooks.md b/packages/cli/docs/webhooks.md index 959863de9..441454b6e 100644 --- a/packages/cli/docs/webhooks.md +++ b/packages/cli/docs/webhooks.md @@ -585,7 +585,7 @@ Replay a batch of webhook events ```bash epilot webhooks batchReplayEvents \ -p configId=7hj28aasgag2gha2 \ - -d '{"eventIds":["2f1b7cf8-ff55-4359-966f-e56f39a52c94","48c984bf-466b-470b-b743-d07cea168243"]}' + -d '{"eventIds":["2f1b7cf8-ff55-4359-966f-e56f39a52c94","48c984bf-466b-470b-b743-d07cea168243"],"reapply_transform":false}' ``` Using positional args for path parameters: @@ -674,7 +674,10 @@ epilot webhooks getEventById -p configId=7hj28aasgag2gha2 -p eventId=123e4567-e8 "status": "succeeded", "http_method": "GET", "payload": "string", - "retry_attempt": 0 + "original_payload": "string", + "retry_attempt": 0, + "can_reapply_transform": true, + "can_reapply_transform_reason": "available" } ``` @@ -695,12 +698,15 @@ Replay a webhook event | `configId` | path | string | Yes | Short uuid to identify the webhook configuration. | | `eventId` | path | string | Yes | Event id | +**Request Body** + **Sample Call** ```bash epilot webhooks replayEvent \ -p configId=7hj28aasgag2gha2 \ - -p eventId=123e4567-e89b-12d3-a456-426614174000 + -p eventId=123e4567-e89b-12d3-a456-426614174000 \ + -d '{"reapply_transform":false}' ``` Using positional args for path parameters: @@ -709,6 +715,12 @@ Using positional args for path parameters: epilot webhooks replayEvent 7hj28aasgag2gha2 123e4567-e89b-12d3-a456-426614174000 ``` +Using stdin pipe: + +```bash +cat body.json | epilot webhooks replayEvent -p configId=7hj28aasgag2gha2 -p eventId=123e4567-e89b-12d3-a456-426614174000 +``` + With JSONata filter: ```bash @@ -880,7 +892,10 @@ epilot webhooks getWebhookEventsV2 -p configId=7hj28aasgag2gha2 --jsonata 'data' "status": "succeeded", "http_method": "GET", "payload": "string", - "retry_attempt": 0 + "original_payload": "string", + "retry_attempt": 0, + "can_reapply_transform": true, + "can_reapply_transform_reason": "available" } ], "next_cursor": { diff --git a/packages/cli/docs/workflow-definition.md b/packages/cli/docs/workflow-definition.md index c4a7580a9..acc72b4e8 100644 --- a/packages/cli/docs/workflow-definition.md +++ b/packages/cli/docs/workflow-definition.md @@ -3,7 +3,9 @@ - **Base URL:** `https://workflows-definition.sls.epilot.io` - **API Docs:** [https://docs.epilot.io/api/workflow-definition](https://docs.epilot.io/api/workflow-definition) -Service for Workflow Definitions for different processes inside of an Organization +The Workflows Definitions API enables you to create, manage, and configure reusable workflow templates +within your organization. Workflow definitions serve as blueprints that define the structure and behavior +of business processes, which can then be instantiated as workflow executions. ## Quick Start @@ -37,8 +39,8 @@ epilot workflow-definition getMaxAllowedLimit **Workflows** - [`getMaxAllowedLimit`](#getmaxallowedlimit) — Get limits and number of created executions for an Organization. -- [`getDefinitions`](#getdefinitions) — Retrieve all Workflow Definitions from an Organization -- [`createDefinition`](#createdefinition) — Create a Workflow Definition. +- [`getDefinitions`](#getdefinitions) — Retrieve all V1 workflow definitions belonging to the authenticated organization. +- [`createDefinition`](#createdefinition) — Create a new V1 workflow definition. The definition consists of sections and steps - [`getDefinition`](#getdefinition) — Get specific Definition by id from the Organization. - [`updateDefinition`](#updatedefinition) — Update Workflow Definition. - [`deleteDefinition`](#deletedefinition) — Delete Workflow Definition. @@ -47,12 +49,12 @@ epilot workflow-definition getMaxAllowedLimit **Flows V2** - [`listFlowTemplates`](#listflowtemplates) — List all Flow Templates for a customer. Optionally, you can filter flow templates by trigger values. -- [`createFlowTemplate`](#createflowtemplate) — Create a new Flow Template. +- [`createFlowTemplate`](#createflowtemplate) — Create a new Flow Template (V2 workflow definition). - [`searchFlowTemplates`](#searchflowtemplates) — Search for flow templates by name, trigger type, enabled status, and more. -- [`getFlowTemplate`](#getflowtemplate) — Get specific FLow template for a customer +- [`getFlowTemplate`](#getflowtemplate) — Retrieve a specific flow template by its unique identifier. - [`updateFlowTemplate`](#updateflowtemplate) — Update Flow Template. - [`deleteFlowTemplate`](#deleteflowtemplate) — Delete Flow Template. -- [`duplicateFlowTemplate`](#duplicateflowtemplate) — Duplicate a Flow Template from an existing workflow. +- [`duplicateFlowTemplate`](#duplicateflowtemplate) — Create a copy of an existing flow template. The duplicated template will have a new **Closing Reason** - [`getAllClosingReasons`](#getallclosingreasons) — Get all Closing Reasons defined in the organization by default all Active. @@ -96,7 +98,7 @@ epilot workflow-definition getMaxAllowedLimit --jsonata 'currentNoOfWorkflows' ### `getDefinitions` -Retrieve all Workflow Definitions from an Organization +Retrieve all V1 workflow definitions belonging to the authenticated organization. `GET /v1/workflows/definitions` @@ -158,7 +160,7 @@ epilot workflow-definition getDefinitions --jsonata '$' ### `createDefinition` -Create a Workflow Definition. +Create a new V1 workflow definition. The definition consists of sections and steps `POST /v1/workflows/definitions` @@ -385,7 +387,9 @@ epilot workflow-definition listFlowTemplates --jsonata 'results[0]' "entity_sync": [], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [] } ] } @@ -397,10 +401,16 @@ epilot workflow-definition listFlowTemplates --jsonata 'results[0]' ### `createFlowTemplate` -Create a new Flow Template. +Create a new Flow Template (V2 workflow definition). `POST /v2/flows/templates` +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `enforce_limits` | query | boolean | No | When true, size/count limit violations (max incoming edges per task, max branches, max entity-sync rules, ...) are enforced as 400 errors. Used by the flow-builder UI for interactive authoring. On upd | + **Request Body** (required) **Sample Call** @@ -525,7 +535,21 @@ epilot workflow-definition createFlowTemplate \ ], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [ + { + "i18nKey": "string", + "message": "string", + "max": 0, + "current": 0, + "node_id": "string", + "task_name": "string", + "branch_name": "string", + "param_name": "string", + "path": [] + } + ] }' ``` @@ -657,7 +681,21 @@ epilot workflow-definition createFlowTemplate --jsonata '$' ], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [ + { + "i18nKey": "string", + "message": "string", + "max": 0, + "current": 0, + "node_id": "string", + "task_name": "string", + "branch_name": "string", + "param_name": "string", + "path": [] + } + ] } ``` @@ -736,7 +774,9 @@ epilot workflow-definition searchFlowTemplates --jsonata 'results[0]' "entity_sync": [], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [] } ] } @@ -748,7 +788,7 @@ epilot workflow-definition searchFlowTemplates --jsonata 'results[0]' ### `getFlowTemplate` -Get specific FLow template for a customer +Retrieve a specific flow template by its unique identifier. `GET /v2/flows/templates/{flowId}` @@ -756,7 +796,7 @@ Get specific FLow template for a customer | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `flowId` | path | string | Yes | | +| `flowId` | path | string | Yes | Unique identifier of the flow template to retrieve. | **Sample Call** @@ -893,7 +933,21 @@ epilot workflow-definition getFlowTemplate -p flowId=7hj28akg --jsonata '$' ], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [ + { + "i18nKey": "string", + "message": "string", + "max": 0, + "current": 0, + "node_id": "string", + "task_name": "string", + "branch_name": "string", + "param_name": "string", + "path": [] + } + ] } ``` @@ -911,7 +965,8 @@ Update Flow Template. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `flowId` | path | string | Yes | | +| `flowId` | path | string | Yes | Unique identifier of the flow template to update. | +| `enforce_limits` | query | boolean | No | When true, size/count limit violations (max incoming edges per task, max branches, max entity-sync rules, ...) are enforced as 400 errors. Used by the flow-builder UI for interactive authoring. On upd | **Request Body** (required) @@ -1039,7 +1094,21 @@ epilot workflow-definition updateFlowTemplate \ ], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [ + { + "i18nKey": "string", + "message": "string", + "max": 0, + "current": 0, + "node_id": "string", + "task_name": "string", + "branch_name": "string", + "param_name": "string", + "path": [] + } + ] }' ``` @@ -1177,7 +1246,21 @@ epilot workflow-definition updateFlowTemplate -p flowId=7hj28akg --jsonata '$' ], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [ + { + "i18nKey": "string", + "message": "string", + "max": 0, + "current": 0, + "node_id": "string", + "task_name": "string", + "branch_name": "string", + "param_name": "string", + "path": [] + } + ] } ``` @@ -1195,7 +1278,7 @@ Delete Flow Template. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `flowId` | path | string | Yes | Id of the flow template to de deleted. | +| `flowId` | path | string | Yes | Unique identifier of the flow template to be deleted. | **Sample Call** @@ -1220,7 +1303,7 @@ epilot workflow-definition deleteFlowTemplate -p flowId=7hj28akg --jsonata '$' ### `duplicateFlowTemplate` -Duplicate a Flow Template from an existing workflow. +Create a copy of an existing flow template. The duplicated template will have a new `POST /v2/flows/templates/{flowId}/duplicate` @@ -1228,7 +1311,7 @@ Duplicate a Flow Template from an existing workflow. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `flowId` | path | string | Yes | | +| `flowId` | path | string | Yes | Unique identifier of the flow template to duplicate. | **Sample Call** @@ -1365,7 +1448,21 @@ epilot workflow-definition duplicateFlowTemplate -p flowId=7hj28akg --jsonata '$ ], "taxonomies": ["string"], "singleClosingReasonSelection": true, - "_manifest": ["string"] + "_manifest": ["string"], + "linear": true, + "limit_warnings": [ + { + "i18nKey": "string", + "message": "string", + "max": 0, + "current": 0, + "node_id": "string", + "task_name": "string", + "branch_name": "string", + "param_name": "string", + "path": [] + } + ] } ``` @@ -1675,7 +1772,7 @@ Delete Workflow Definition. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `definitionId` | path | string | Yes | Id of the definition to de deleted. | +| `definitionId` | path | string | Yes | Unique identifier of the workflow definition to be deleted. | **Sample Call** @@ -1708,7 +1805,7 @@ Get all Closing Reasons defined in the organization by default all Active. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `includeInactive` | query | boolean | No | Filter Closing Reasons by status like active inactiv | +| `includeInactive` | query | boolean | No | When set to true, includes inactive closing reasons in the response. By default, only active closing reasons are returned. | **Sample Call** diff --git a/packages/cli/docs/workflow.md b/packages/cli/docs/workflow.md index bfe67f8b3..a91aa3902 100644 --- a/packages/cli/docs/workflow.md +++ b/packages/cli/docs/workflow.md @@ -3,7 +3,10 @@ - **Base URL:** `https://workflows-execution.sls.epilot.io` - **API Docs:** [https://docs.epilot.io/api/workflow](https://docs.epilot.io/api/workflow) -Service for Workflow Executions which covers executions of processes defined in an Organization +Overview +The Workflows Executions API manages the runtime instances of workflow processes within an organization. +While **Workflow Definitions** (managed by the Workflows Definitions API) serve as reusable templates +that define the structure, phases, and tasks of a process, **Workflow Executions** a ## Quick Start @@ -37,31 +40,32 @@ epilot workflow getExecutions **Workflow Executions** - [`getExecutions`](#getexecutions) — Retrieve Workflow Executions. Optionally, you can filter them by context & schema. Please be aware, these executions are -- [`createExecution`](#createexecution) — Create a Workflow Execution. Start a new workflow execution, based on a workflow definition (template). -- [`getExecution`](#getexecution) — Get a full workflow execution, included steps information, by execution id. +- [`createExecution`](#createexecution) — Creates a new V1 Workflow Execution from a workflow definition (template). +- [`getExecution`](#getexecution) — Retrieves a complete V1 workflow execution by ID, including all steps information. - [`updateExecution`](#updateexecution) — Patches updates like assignees, status, closingReason for a single Workflow Execution. - [`deleteExecution`](#deleteexecution) — Delete workflow execution by id. Workflow contexts will NOT be deleted. **Workflow Steps** - [`createStep`](#createstep) — Create a new step in current workflow execution. -- [`updateStep`](#updatestep) — Patches various changes to a workflow execution step. +- [`updateStep`](#updatestep) — Updates a workflow execution step with new values for status, assignees, due date, position, and more. - [`deleteStep`](#deletestep) — Deletes a step from a workflow execution. **Closing Reasons** - [`getClosingReasonExecution`](#getclosingreasonexecution) — Shows all Closing Reasons defined at the moment of starting the Workflow Execution. **Flows V2** -- [`startFlowExecution`](#startflowexecution) — Starts a new Flow Execution based on a flow template. -- [`getFlowExecution`](#getflowexecution) — Get a full flow execution, included tasks, phases, edges & analytics. +- [`startFlowExecution`](#startflowexecution) — Starts a new Flow Execution based on a flow template (definition). +- [`getFlowExecution`](#getflowexecution) — Retrieves a complete flow execution by ID, including all phases, tasks, edges, contexts, and analytics. - [`patchFlowExecution`](#patchflowexecution) — Patch flow execution with new assignees, status, analytics & other changes. - [`deleteFlowExecution`](#deleteflowexecution) — Deletes a specific execution of a flow, identified by id. Flow contexts will NOT be deleted. - [`searchFlowExecutions`](#searchflowexecutions) — Search Flow Executions for a specific Entity. -- [`patchTask`](#patchtask) — Changes various attributes of a flow task, like assignees, status, due date, etc. +- [`patchTask`](#patchtask) — Updates attributes of a flow task including status, assignees, due date, and more. - [`runTaskAutomation`](#runtaskautomation) — Runs configured automation for a flow task - [`executeTask`](#executetask) — Executes any kind of flow task immediately. - [`patchPhase`](#patchphase) — Apply updates to a phase within flow execution - [`addTask`](#addtask) — Create a new task in current workflow execution. - [`cancelTaskSchedule`](#canceltaskschedule) — Cancels a scheduled task, deleting the schedule and marking the task as skipped. +- [`reconcileAutomationTask`](#reconcileautomationtask) — Reconciles an automation task's status against its linked automation execution. - [`runTaskScheduleNow`](#runtaskschedulenow) — Cancels the pending schedule for a task and immediately triggers its automation execution. ### `getExecutions` @@ -138,7 +142,7 @@ epilot workflow getExecutions --jsonata '$' ### `createExecution` -Create a Workflow Execution. Start a new workflow execution, based on a workflow definition (template). +Creates a new V1 Workflow Execution from a workflow definition (template). `POST /v1/workflows/executions` @@ -232,7 +236,7 @@ epilot workflow createExecution --jsonata '$' ### `getExecution` -Get a full workflow execution, included steps information, by execution id. +Retrieves a complete V1 workflow execution by ID, including all steps information. `GET /v1/workflows/executions/{executionId}` @@ -572,7 +576,7 @@ epilot workflow createStep -p executionId=wd56125gah --jsonata '$' ### `updateStep` -Patches various changes to a workflow execution step. +Updates a workflow execution step with new values for status, assignees, due date, position, and more. `PATCH /v1/workflows/executions/{executionId}/steps/{stepId}` @@ -580,8 +584,8 @@ Patches various changes to a workflow execution step. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `executionId` | path | string | Yes | Id of the execution | -| `stepId` | path | string | Yes | Short uuid (length 6) to identify the Workflow Execution Step. | +| `executionId` | path | string | Yes | Unique identifier of the workflow execution | +| `stepId` | path | string | Yes | Short unique identifier (typically 6 characters) of the step within the execution | **Request Body** (required) @@ -828,7 +832,7 @@ epilot workflow getClosingReasonExecution -p executionId=wd561 --jsonata 'reason ### `startFlowExecution` -Starts a new Flow Execution based on a flow template. +Starts a new Flow Execution based on a flow template (definition). `POST /v2/flows/executions` @@ -845,7 +849,7 @@ With request body: ```bash epilot workflow startFlowExecution \ -d '{ - "flow_template_id": "string", + "flow_template_id": "tpl_abc123def456", "trigger": { "type": "MANUAL", "automation_config": { @@ -856,7 +860,9 @@ epilot workflow startFlowExecution \ "input_context": { "source": "trigger", "task_id": "string" - } + }, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" } }, "contexts": [ @@ -919,7 +925,8 @@ epilot workflow startFlowExecution --jsonata 'id' ], "crt_tasks": [ { - "id": "string" + "id": "string", + "crt_since": "1970-01-01T00:00:00.000Z" } ], "phases": [ @@ -1002,10 +1009,13 @@ epilot workflow startFlowExecution --jsonata 'id' "execution_id": "string", "execution_status": "string", "error_reason": "string", - "input_context": {} + "input_context": {}, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" } }, - "singleClosingReasonSelection": true + "singleClosingReasonSelection": true, + "linear": true } ``` @@ -1015,7 +1025,7 @@ epilot workflow startFlowExecution --jsonata 'id' ### `getFlowExecution` -Get a full flow execution, included tasks, phases, edges & analytics. +Retrieves a complete flow execution by ID, including all phases, tasks, edges, contexts, and analytics. `GET /v2/flows/executions/{execution_id}` @@ -1023,25 +1033,27 @@ Get a full flow execution, included tasks, phases, edges & analytics. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | **Sample Call** ```bash epilot workflow getFlowExecution \ - -p execution_id=wd561 + -p execution_id=exec_abc123def456 ``` Using positional args for path parameters: ```bash -epilot workflow getFlowExecution wd561 +epilot workflow getFlowExecution exec_abc123def456 ``` With JSONata filter: ```bash -epilot workflow getFlowExecution -p execution_id=wd561 --jsonata 'id' +epilot workflow getFlowExecution -p execution_id=exec_abc123def456 --jsonata 'id' ```
@@ -1081,7 +1093,8 @@ epilot workflow getFlowExecution -p execution_id=wd561 --jsonata 'id' ], "crt_tasks": [ { - "id": "string" + "id": "string", + "crt_since": "1970-01-01T00:00:00.000Z" } ], "phases": [ @@ -1164,10 +1177,13 @@ epilot workflow getFlowExecution -p execution_id=wd561 --jsonata 'id' "execution_id": "string", "execution_status": "string", "error_reason": "string", - "input_context": {} + "input_context": {}, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" } }, - "singleClosingReasonSelection": true + "singleClosingReasonSelection": true, + "linear": true } ``` @@ -1185,7 +1201,9 @@ Patch flow execution with new assignees, status, analytics & other changes. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | **Request Body** (required) @@ -1193,14 +1211,14 @@ Patch flow execution with new assignees, status, analytics & other changes. ```bash epilot workflow patchFlowExecution \ - -p execution_id=wd561 + -p execution_id=exec_abc123def456 ``` With request body: ```bash epilot workflow patchFlowExecution \ - -p execution_id=wd561 \ + -p execution_id=exec_abc123def456 \ -d '{ "status": "STARTED", "assigned_to": ["string"], @@ -1240,19 +1258,19 @@ epilot workflow patchFlowExecution \ Using positional args for path parameters: ```bash -epilot workflow patchFlowExecution wd561 +epilot workflow patchFlowExecution exec_abc123def456 ``` Using stdin pipe: ```bash -cat body.json | epilot workflow patchFlowExecution -p execution_id=wd561 +cat body.json | epilot workflow patchFlowExecution -p execution_id=exec_abc123def456 ``` With JSONata filter: ```bash -epilot workflow patchFlowExecution -p execution_id=wd561 --jsonata 'id' +epilot workflow patchFlowExecution -p execution_id=exec_abc123def456 --jsonata 'id' ```
@@ -1292,7 +1310,8 @@ epilot workflow patchFlowExecution -p execution_id=wd561 --jsonata 'id' ], "crt_tasks": [ { - "id": "string" + "id": "string", + "crt_since": "1970-01-01T00:00:00.000Z" } ], "phases": [ @@ -1375,10 +1394,13 @@ epilot workflow patchFlowExecution -p execution_id=wd561 --jsonata 'id' "execution_id": "string", "execution_status": "string", "error_reason": "string", - "input_context": {} + "input_context": {}, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" } }, - "singleClosingReasonSelection": true + "singleClosingReasonSelection": true, + "linear": true } ``` @@ -1396,26 +1418,30 @@ Deletes a specific execution of a flow, identified by id. Flow contexts will NOT | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | -| `soft` | query | boolean | No | If true, the execution will NOT be deleted permanently, but rather kept for archive purpose. | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `soft` | query | boolean | No | When true, the execution is marked as deleted but retained in storage for archival +and audit purposes. When false (default), the execution is permanently removed. +Soft-deleted executions do not appear | **Sample Call** ```bash epilot workflow deleteFlowExecution \ - -p execution_id=wd561 + -p execution_id=exec_abc123def456 ``` Using positional args for path parameters: ```bash -epilot workflow deleteFlowExecution wd561 +epilot workflow deleteFlowExecution exec_abc123def456 ``` With JSONata filter: ```bash -epilot workflow deleteFlowExecution -p execution_id=wd561 --jsonata '$' +epilot workflow deleteFlowExecution -p execution_id=exec_abc123def456 --jsonata '$' ``` --- @@ -1476,7 +1502,8 @@ epilot workflow searchFlowExecutions --jsonata 'results[0]' "entity_sync": [], "taxonomies": ["string"], "trigger": {}, - "singleClosingReasonSelection": true + "singleClosingReasonSelection": true, + "linear": true } ] } @@ -1488,7 +1515,7 @@ epilot workflow searchFlowExecutions --jsonata 'results[0]' ### `patchTask` -Changes various attributes of a flow task, like assignees, status, due date, etc. +Updates attributes of a flow task including status, assignees, due date, and more. `PATCH /v2/flows/executions/{execution_id}/tasks/{task_id}` @@ -1496,8 +1523,12 @@ Changes various attributes of a flow task, like assignees, status, due date, etc | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | -| `task_id` | path | string | Yes | Id of the task | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `task_id` | path | string | Yes | Unique identifier for a task within the execution. Each task has a unique ID +that distinguishes it from other tasks in the same execution. + | **Request Body** (required) @@ -1505,20 +1536,20 @@ Changes various attributes of a flow task, like assignees, status, due date, etc ```bash epilot workflow patchTask \ - -p execution_id=wd561 \ - -p task_id=7hj28a + -p execution_id=exec_abc123def456 \ + -p task_id=task_7hj28a ``` With request body: ```bash epilot workflow patchTask \ - -p execution_id=wd561 \ - -p task_id=7hj28a \ + -p execution_id=exec_abc123def456 \ + -p task_id=task_7hj28a \ -d '{ - "name": "string", + "name": "Review customer application", "status": "UNASSIGNED", - "due_date": "2021-04-27T12:00:00.000Z", + "due_date": "2026-05-28T00:00:00.000", "due_date_config": { "duration": 0, "unit": "minutes", @@ -1536,7 +1567,9 @@ epilot workflow patchTask \ "input_context": { "source": "trigger", "task_id": "string" - } + }, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" }, "description": { "enabled": true, @@ -1569,27 +1602,28 @@ epilot workflow patchTask \ "label": "string", "description": "string" }, - "next_condition_id": "string", - "revert_execution": false + "next_condition_id": "cond_branch_approved", + "revert_execution": false, + "completed_via_journey": false }' ``` Using positional args for path parameters: ```bash -epilot workflow patchTask wd561 7hj28a +epilot workflow patchTask exec_abc123def456 task_7hj28a ``` Using stdin pipe: ```bash -cat body.json | epilot workflow patchTask -p execution_id=wd561 -p task_id=7hj28a +cat body.json | epilot workflow patchTask -p execution_id=exec_abc123def456 -p task_id=task_7hj28a ``` With JSONata filter: ```bash -epilot workflow patchTask -p execution_id=wd561 -p task_id=7hj28a --jsonata '$' +epilot workflow patchTask -p execution_id=exec_abc123def456 -p task_id=task_7hj28a --jsonata '$' ```
@@ -1692,27 +1726,31 @@ Runs configured automation for a flow task | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | -| `task_id` | path | string | Yes | Id of the task | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `task_id` | path | string | Yes | Unique identifier for a task within the execution. Each task has a unique ID +that distinguishes it from other tasks in the same execution. + | **Sample Call** ```bash epilot workflow runTaskAutomation \ - -p execution_id=wd561 \ - -p task_id=7hj28a + -p execution_id=exec_abc123def456 \ + -p task_id=task_7hj28a ``` Using positional args for path parameters: ```bash -epilot workflow runTaskAutomation wd561 7hj28a +epilot workflow runTaskAutomation exec_abc123def456 task_7hj28a ``` With JSONata filter: ```bash -epilot workflow runTaskAutomation -p execution_id=wd561 -p task_id=7hj28a --jsonata '$' +epilot workflow runTaskAutomation -p execution_id=exec_abc123def456 -p task_id=task_7hj28a --jsonata '$' ```
@@ -1802,7 +1840,9 @@ epilot workflow runTaskAutomation -p execution_id=wd561 -p task_id=7hj28a --json "input_context": { "source": "trigger", "task_id": "string" - } + }, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" }, "automation_execution_id": "string", "trigger_mode": "manual", @@ -1830,27 +1870,31 @@ Executes any kind of flow task immediately. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | -| `task_id` | path | string | Yes | Id of the task | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `task_id` | path | string | Yes | Unique identifier for a task within the execution. Each task has a unique ID +that distinguishes it from other tasks in the same execution. + | **Sample Call** ```bash epilot workflow executeTask \ - -p execution_id=wd561 \ - -p task_id=7hj28a + -p execution_id=exec_abc123def456 \ + -p task_id=task_7hj28a ``` Using positional args for path parameters: ```bash -epilot workflow executeTask wd561 7hj28a +epilot workflow executeTask exec_abc123def456 task_7hj28a ``` With JSONata filter: ```bash -epilot workflow executeTask -p execution_id=wd561 -p task_id=7hj28a --jsonata '$' +epilot workflow executeTask -p execution_id=exec_abc123def456 -p task_id=task_7hj28a --jsonata '$' ```
@@ -1953,8 +1997,12 @@ Apply updates to a phase within flow execution | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | -| `phase_id` | path | string | Yes | Id of the phase | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `phase_id` | path | string | Yes | Unique identifier for a phase within the execution. Phases group related tasks +together and track collective progress. + | **Request Body** (required) @@ -1962,16 +2010,16 @@ Apply updates to a phase within flow execution ```bash epilot workflow patchPhase \ - -p execution_id=wd561 \ - -p phase_id=9gjs2952j + -p execution_id=exec_abc123def456 \ + -p phase_id=phase_9gjs2952j ``` With request body: ```bash epilot workflow patchPhase \ - -p execution_id=wd561 \ - -p phase_id=9gjs2952j \ + -p execution_id=exec_abc123def456 \ + -p phase_id=phase_9gjs2952j \ -d '{ "name": "string", "due_date": "2021-04-27T12:00:00.000Z", @@ -1989,19 +2037,19 @@ epilot workflow patchPhase \ Using positional args for path parameters: ```bash -epilot workflow patchPhase wd561 9gjs2952j +epilot workflow patchPhase exec_abc123def456 phase_9gjs2952j ``` Using stdin pipe: ```bash -cat body.json | epilot workflow patchPhase -p execution_id=wd561 -p phase_id=9gjs2952j +cat body.json | epilot workflow patchPhase -p execution_id=exec_abc123def456 -p phase_id=phase_9gjs2952j ``` With JSONata filter: ```bash -epilot workflow patchPhase -p execution_id=wd561 -p phase_id=9gjs2952j --jsonata 'id' +epilot workflow patchPhase -p execution_id=exec_abc123def456 -p phase_id=phase_9gjs2952j --jsonata 'id' ```
@@ -2055,7 +2103,9 @@ Create a new task in current workflow execution. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | **Request Body** (required) @@ -2063,14 +2113,14 @@ Create a new task in current workflow execution. ```bash epilot workflow addTask \ - -p execution_id=wd561 + -p execution_id=exec_abc123def456 ``` With request body: ```bash epilot workflow addTask \ - -p execution_id=wd561 \ + -p execution_id=exec_abc123def456 \ -d '{ "previous_task_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "next_task_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", @@ -2096,7 +2146,9 @@ epilot workflow addTask \ "input_context": { "source": "trigger", "task_id": "string" - } + }, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" }, "phase_id": "string", "task_type": "MANUAL" @@ -2107,19 +2159,19 @@ epilot workflow addTask \ Using positional args for path parameters: ```bash -epilot workflow addTask wd561 +epilot workflow addTask exec_abc123def456 ``` Using stdin pipe: ```bash -cat body.json | epilot workflow addTask -p execution_id=wd561 +cat body.json | epilot workflow addTask -p execution_id=exec_abc123def456 ``` With JSONata filter: ```bash -epilot workflow addTask -p execution_id=wd561 --jsonata '$' +epilot workflow addTask -p execution_id=exec_abc123def456 --jsonata '$' ```
@@ -2222,29 +2274,205 @@ Cancels a scheduled task, deleting the schedule and marking the task as skipped. | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | -| `task_id` | path | string | Yes | Id of the task | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `task_id` | path | string | Yes | Unique identifier for a task within the execution. Each task has a unique ID +that distinguishes it from other tasks in the same execution. + | **Sample Call** ```bash epilot workflow cancelTaskSchedule \ - -p execution_id=wd561 \ - -p task_id=7hj28a + -p execution_id=exec_abc123def456 \ + -p task_id=task_7hj28a +``` + +Using positional args for path parameters: + +```bash +epilot workflow cancelTaskSchedule exec_abc123def456 task_7hj28a +``` + +With JSONata filter: + +```bash +epilot workflow cancelTaskSchedule -p execution_id=exec_abc123def456 -p task_id=task_7hj28a --jsonata '$' +``` + +--- + +### `reconcileAutomationTask` + +Reconciles an automation task's status against its linked automation execution. + +`POST /v2/flows/executions/{execution_id}/tasks/{task_id}/reconcile-automation` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `task_id` | path | string | Yes | Unique identifier for a task within the execution. Each task has a unique ID +that distinguishes it from other tasks in the same execution. + | + +**Sample Call** + +```bash +epilot workflow reconcileAutomationTask \ + -p execution_id=exec_abc123def456 \ + -p task_id=task_7hj28a ``` Using positional args for path parameters: ```bash -epilot workflow cancelTaskSchedule wd561 7hj28a +epilot workflow reconcileAutomationTask exec_abc123def456 task_7hj28a ``` With JSONata filter: ```bash -epilot workflow cancelTaskSchedule -p execution_id=wd561 -p task_id=7hj28a --jsonata '$' +epilot workflow reconcileAutomationTask -p execution_id=exec_abc123def456 -p task_id=task_7hj28a --jsonata 'id' +``` + +
+Sample Response + +```json +{ + "id": "string", + "flow_template_id": "string", + "org_id": "string", + "name": "string", + "created_at": "string", + "updated_at": "string", + "due_date": "string", + "due_date_config": { + "duration": 0, + "unit": "minutes", + "type": "WORKFLOW_STARTED", + "task_id": "string", + "phase_id": "string" + }, + "status": "STARTED", + "assigned_to": ["string"], + "analytics": { + "started_at": "string", + "completed_at": "string", + "closed_at": "string", + "started_by": "string", + "closed_by": "string" + }, + "contexts": [ + { + "entity_id": "string", + "entity_schema": "string", + "is_primary": false + } + ], + "crt_tasks": [ + { + "id": "string", + "crt_since": "1970-01-01T00:00:00.000Z" + } + ], + "phases": [ + { + "id": "string", + "template_id": "string", + "name": "string", + "status": "OPEN", + "updated_at": "string", + "due_date": "2021-04-27T12:00:00.000Z", + "due_date_config": {}, + "assigned_to": ["string"], + "analytics": {}, + "taxonomies": ["string"], + "loop_config": {} + } + ], + "tasks": [ + { + "id": "string", + "template_id": "string", + "name": "string", + "description": {}, + "status": "UNASSIGNED", + "journey": {}, + "due_date": "2021-04-27T12:00:00.000Z", + "due_date_config": {}, + "requirements": [], + "assigned_to": ["string"], + "analytics": {}, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "manually_created": true, + "enabled": true, + "ecp": {}, + "installer": {}, + "partner": {}, + "taxonomies": ["string"], + "phase_id": "string", + "task_type": "MANUAL", + "loop_config": {} + } + ], + "edges": [ + { + "id": "string", + "from_id": "string", + "to_id": "string", + "condition_id": "abc123", + "none_met": true + } + ], + "_execution_chain": { + "parent_execution_id": "string", + "parent_task_id": "string", + "depth": 0 + }, + "closing_reason": { + "selected_reasons": [ + {} + ], + "configured_reasons": [ + {} + ], + "extra_description": "string" + }, + "available_in_ecp": true, + "entity_sync": [ + { + "trigger": {}, + "target": {}, + "value": {} + } + ], + "taxonomies": ["string"], + "trigger": { + "type": "MANUAL", + "automation_config": { + "flow_id": "string", + "execution_id": "string", + "execution_status": "string", + "error_reason": "string", + "input_context": {}, + "heal_attempts": 0, + "last_heal_attempted_at": "1970-01-01T00:00:00.000Z" + } + }, + "singleClosingReasonSelection": true, + "linear": true +} ``` +
+ --- ### `runTaskScheduleNow` @@ -2257,27 +2485,31 @@ Cancels the pending schedule for a task and immediately triggers its automation | Name | In | Type | Required | Description | | ---- | -- | ---- | -------- | ----------- | -| `execution_id` | path | string | Yes | Id of the execution | -| `task_id` | path | string | Yes | Id of the task | +| `execution_id` | path | string | Yes | Unique identifier for the flow execution. This ID is generated when the execution +is created and remains constant throughout its lifecycle. + | +| `task_id` | path | string | Yes | Unique identifier for a task within the execution. Each task has a unique ID +that distinguishes it from other tasks in the same execution. + | **Sample Call** ```bash epilot workflow runTaskScheduleNow \ - -p execution_id=wd561 \ - -p task_id=7hj28a + -p execution_id=exec_abc123def456 \ + -p task_id=task_7hj28a ``` Using positional args for path parameters: ```bash -epilot workflow runTaskScheduleNow wd561 7hj28a +epilot workflow runTaskScheduleNow exec_abc123def456 task_7hj28a ``` With JSONata filter: ```bash -epilot workflow runTaskScheduleNow -p execution_id=wd561 -p task_id=7hj28a --jsonata '$' +epilot workflow runTaskScheduleNow -p execution_id=exec_abc123def456 -p task_id=task_7hj28a --jsonata '$' ```
diff --git a/packages/cli/src/commands/apis/ai-agents.ts b/packages/cli/src/commands/apis/ai-agents.ts index 436465123..0dce909ec 100644 --- a/packages/cli/src/commands/apis/ai-agents.ts +++ b/packages/cli/src/commands/apis/ai-agents.ts @@ -3,7 +3,7 @@ import { defineCommand } from 'citty'; import { callApi } from '../../lib/call.js'; export default defineCommand({ - meta: { name: 'ai-agents', description: 'AI Agents API - OpenAPI 3.0' }, + meta: { name: 'ai-agents', description: 'AI Agents API' }, args: { operation: { type: 'positional', description: 'operationId to call', required: false }, param: { type: 'string', alias: 'p', description: 'Parameter key=value' }, diff --git a/packages/cli/src/generated/api-list.ts b/packages/cli/src/generated/api-list.ts index 350c8606e..5ffcc11ca 100644 --- a/packages/cli/src/generated/api-list.ts +++ b/packages/cli/src/generated/api-list.ts @@ -46,9 +46,9 @@ export const API_LIST: ApiInfo[] = [ { apiName: 'aiAgents', kebabName: 'ai-agents', - title: 'AI Agents API - OpenAPI 3.0', + title: 'AI Agents API', serverUrl: 'https://ai-agents.sls.epilot.io', - operationCount: 12, + operationCount: 21, operationIds: [ 'listAgents', 'createAgent', @@ -56,12 +56,21 @@ export const API_LIST: ApiInfo[] = [ 'updateAgentById', 'deleteAgentById', 'executeAgent', + 'executeAgentStream', 'listExecutions', 'getExecution', 'cancelExecution', 'getExecutionTrace', + 'getExecutionFeedback', + 'putExecutionFeedback', 'approveExecution', 'rejectExecution', + 'streamExecution', + 'chat', + 'listConversations', + 'getConversation', + 'deleteConversation', + 'submitConversationFeedback', ], }, { @@ -69,7 +78,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'app', title: 'App API', serverUrl: 'https://app.sls.epilot.io', - operationCount: 32, + operationCount: 36, operationIds: [ 'getPublicFacingComponent', 'listConfigurations', @@ -100,9 +109,13 @@ export const API_LIST: ApiInfo[] = [ 'patchInstallation', 'uninstall', 'promoteVersion', + 'resolveOptions', 'ingestEvent', 'publicProxyGet', 'publicProxyPost', + 'publicProxyPut', + 'publicProxyPatch', + 'publicProxyDelete', ], }, { @@ -118,7 +131,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'automation', title: 'Automation API', serverUrl: 'https://automation.sls.epilot.io', - operationCount: 16, + operationCount: 17, operationIds: [ 'searchFlows', 'createFlow', @@ -128,6 +141,7 @@ export const API_LIST: ApiInfo[] = [ 'deleteFlow', 'getExecutions', 'startExecution', + 'searchExecutions', 'bulkTriggerExecutions', 'getBulkJob', 'patchBulkJob', @@ -143,7 +157,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'billing', title: 'Billing API', serverUrl: 'https://billing.sls.epilot.io', - operationCount: 10, + operationCount: 14, operationIds: [ 'getBillingEvents', 'createBillingEvent', @@ -154,6 +168,10 @@ export const API_LIST: ApiInfo[] = [ 'createContractEntity', 'updateContractEntity', 'deleteContractEntity', + 'getContractPricingInformation', + 'getBillingAccountPricingInformation', + 'getContractConfigurationHistory', + 'getBillingAccountConfigurationHistory', 'getCustomerBalance', ], }, @@ -162,7 +180,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'blueprint-manifest', title: 'Blueprint Manifest API', serverUrl: 'https://blueprint-manifest.sls.epilot.io', - operationCount: 61, + operationCount: 73, operationIds: [ 'getJob', 'createExport', @@ -183,6 +201,9 @@ export const API_LIST: ApiInfo[] = [ 'getBlueprint', 'updateBlueprint', 'deleteBlueprint', + 'addBlueprintNote', + 'updateBlueprintNote', + 'deleteBlueprintNote', 'validateBlueprint', 'verifyBlueprint', 'detectPatchChanges', @@ -206,6 +227,7 @@ export const API_LIST: ApiInfo[] = [ 'listBlueprintJobs', 'getBlueprintJob', 'continueInstallationJob', + 'retryInstallationJob', 'cancelBlueprintJob', 'getMarketplaceListing', 'createMarketplaceListing', @@ -217,10 +239,18 @@ export const API_LIST: ApiInfo[] = [ 'createMarketplaceListingVersion', 'updateMarketplaceListingVersion', 'publishMarketplaceListingVersion', + 'publishBlueprintV3', + 'preInstallBlueprintV3', 'installBlueprintV3', 'restoreBlueprintDeploymentV3', 'getRestorePreview', + 'triggerDeploymentHealthCheckV3', + 'getDeploymentHealthReportV3', 'getBlueprintLineageV3', + 'createBulkInstallV3', + 'getBulkInstallV3', + 'listBulkInstallTargetsV3', + 'retryBulkInstallTargetV3', 'listUniquenessCriteria', 'getUniquenessCriteria', 'putUniquenessCriteria', @@ -232,8 +262,37 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'calendar', title: 'Calendar API', serverUrl: 'https://calendar.sls.epilot.io', - operationCount: 4, - operationIds: ['listCalendars', 'getCalendar', 'listEvents', 'getEvent'], + operationCount: 28, + operationIds: [ + 'listUsersAbsence', + 'searchAbsence', + 'searchNowAbsence', + 'listAbsenceAdjustments', + 'createAbsenceAdjustment', + 'getAbsenceAdjustment', + 'patchAbsenceAdjustment', + 'deleteAbsenceAdjustment', + 'getUserAbsence', + 'getWorkingHours', + 'putWorkingHours', + 'deleteWorkingHours', + 'listCalendars', + 'createCalendar', + 'addOutlookCalendar', + 'listOutlookCalendars', + 'deleteOutlookCalendar', + 'outlookWebhook', + 'getCalendar', + 'updateCalendar', + 'deleteCalendar', + 'listEvents', + 'createEvent', + 'getEvent', + 'updateEvent', + 'deleteEvent', + 'shareEvent', + 'unshareEvent', + ], }, { apiName: 'configurationHub', @@ -278,7 +337,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'customer-portal', title: 'Portal API', serverUrl: 'https://customer-portal-api.sls.epilot.io', - operationCount: 149, + operationCount: 167, operationIds: [ 'upsertPortal', 'createUser', @@ -288,6 +347,8 @@ export const API_LIST: ApiInfo[] = [ 'getPortalConfigByDomain', 'getPortalConfig', 'deletePortal', + 'createExport', + 'getExport', 'getPortalExtensions', 'getPublicPortalExtensionDetails', 'getPortalExtensionsV3', @@ -309,6 +370,8 @@ export const API_LIST: ApiInfo[] = [ 'getAllPortalConfigs', 'getEmailTemplates', 'upsertEmailTemplates', + 'migrateEmailTemplateReferences', + 'listEmailTemplateReferences', 'getEmailTemplatesByPortalId', 'upsertEmailTemplatesByPortalId', 'getPublicPortalWidgets', @@ -338,6 +401,7 @@ export const API_LIST: ApiInfo[] = [ 'updatePortalUser', 'deletePortalUser', 'updatePortalUserEmail', + 'changePortalUserPassword', 'resendConfirmationEmail', 'fetchPortalUsersByRelatedEntity', 'confirmUser', @@ -360,6 +424,7 @@ export const API_LIST: ApiInfo[] = [ 'getAllContracts', 'getContract', 'updateContract', + 'getContractWithTemplates', 'addContractByIdentifiers', 'getEntityIdentifiers', 'getEntityActivityFeed', @@ -372,6 +437,7 @@ export const API_LIST: ApiInfo[] = [ 'getRegistrationIdentifiers', 'getAllFiles', 'getFileById', + 'getFilePreview', 'trackFileDownloaded', 'getFilesCountByEntity', 'getBillingEvents', @@ -382,10 +448,15 @@ export const API_LIST: ApiInfo[] = [ 'triggerEntityAccessEventV3', 'getPortalUserEntity', 'searchPortalUserEntities', + 'createPortalUserEntity', + 'patchPortalUserEntity', 'canTriggerPortalFlow', 'getAutomationContext', 'updateWorkflowStepAsDone', 'getEntityWorkflows', + 'getOutstandingTasks', + 'getEntityPortalWorkflows', + 'getEntityPortalWorkflowsBatch', 'uploadMeterReadingPhoto', 'createMeterReading', 'getAllowedMeterReadingRange', @@ -411,6 +482,10 @@ export const API_LIST: ApiInfo[] = [ 'deletePortalPageBlock', 'getUserEntryPoint', 'updateCampaignPortalBlockStatus', + 'listPortalNotifications', + 'getPortalNotificationsUnreadCount', + 'markAllPortalNotificationsRead', + 'markPortalNotificationRead', 'updateNotificationsStatus', 'deRegisterMLoginUser', 'notifyMLoginInterestChange', @@ -429,6 +504,8 @@ export const API_LIST: ApiInfo[] = [ 'enablePartner', 'verifyDns', 'portalProxyExecute', + 'getMobileConfig', + 'putMobileConfig', ], }, { @@ -436,13 +513,24 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'dashboard', title: 'Dashboard API', serverUrl: 'https://dashboard.sls.epilot.io', - operationCount: 7, + operationCount: 18, operationIds: [ 'listDashboards', 'createDashboard', + 'listFavoriteDashboardIds', + 'favoriteDashboard', + 'unfavoriteDashboard', 'getDashboard', 'putDashboard', + 'patchDashboard', 'deleteDashboard', + 'listInsights', + 'createInsight', + 'listInsightTags', + 'getInsight', + 'putInsight', + 'patchInsight', + 'deleteInsight', 'listAvailableVisualisations', 'listAvailableExamples', ], @@ -471,21 +559,33 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'deduplication', title: 'Deduplication API', serverUrl: 'https://deduplication.sls.epilot.io', - operationCount: 3, - operationIds: ['deduplicate', 'deduplicateAsync', 'getDeduplicationJob'], + operationCount: 10, + operationIds: [ + 'deduplicate', + 'deduplicateAsync', + 'getDeduplicationJob', + 'detectDuplicates', + 'dismissDuplicates', + 'listUniquenessCriteria', + 'createUniquenessCriteria', + 'getUniquenessCriteria', + 'updateUniquenessCriteria', + 'deleteUniquenessCriteria', + ], }, { apiName: 'design', kebabName: 'design', title: 'Design Builder API v2', serverUrl: 'https://design-builder-api.sls.epilot.io', - operationCount: 13, + operationCount: 14, operationIds: [ 'getAllDesigns', 'addDesign', 'getDesign', 'updateDesign', 'deleteDesign', + 'duplicateDesign', 'getThemeFromDesign', 'getFiles', 'uploadFile', @@ -509,7 +609,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'email-settings', title: 'Messaging Settings API', serverUrl: 'https://email-settings.sls.epilot.io', - operationCount: 36, + operationCount: 48, operationIds: [ 'provisionEpilotEmailAddress', 'setEmailAddressPrimary', @@ -526,6 +626,9 @@ export const API_LIST: ApiInfo[] = [ 'listInboxBuckets', 'connectOutlook', 'getOutlookConnectionStatus', + 'getCalendarAdminConsentStatus', + 'getMyCalendarConnection', + 'disconnectMyCalendar', 'disconnectOutlook', 'connectMsTeams', 'disconnectMsTeams', @@ -536,6 +639,15 @@ export const API_LIST: ApiInfo[] = [ 'getMailboxSyncStatus', 'retryMailboxSync', 'getConnectedOutlookEmails', + 'listSmtpConnections', + 'createSmtpConnection', + 'getSmtpConnection', + 'updateSmtpConnection', + 'deleteSmtpConnection', + 'testSmtpConnection', + 'listSmtpSenders', + 'connectSmtpSender', + 'disconnectSmtpSender', 'outlookOAuthCallback', 'getSettings', 'addSetting', @@ -570,7 +682,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'entity', title: 'Entity API', serverUrl: 'https://entity.sls.epilot.io', - operationCount: 87, + operationCount: 88, operationIds: [ 'listSchemas', 'listSchemasV2', @@ -617,6 +729,7 @@ export const API_LIST: ApiInfo[] = [ 'deleteRelation', 'exportEntities', 'importEntities', + 'abortEntityImport', 'listSavedViews', 'createSavedView', 'getSavedView', @@ -704,11 +817,15 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'event-catalog', title: 'Event Catalog API', serverUrl: 'https://event-catalog.sls.epilot.io', - operationCount: 10, + operationCount: 14, operationIds: [ 'listEvents', + 'createCustomEvent', 'getEvent', 'patchEvent', + 'deprecateCustomEvent', + 'previewCustomEvent', + 'publishCustomEventDefinition', 'getEventJSONSchema', 'getEventExample', 'listEventVersions', @@ -723,7 +840,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'file', title: 'File API', serverUrl: 'https://file.sls.epilot.io', - operationCount: 30, + operationCount: 37, operationIds: [ 'uploadFileV2', 'saveFileV2', @@ -736,7 +853,14 @@ export const API_LIST: ApiInfo[] = [ 'downloadFiles', 'createZipJob', 'getZipJob', + 'getFileSummary', + 'getFileSummaryFeedback', + 'putFileSummaryFeedback', + 'createFileSummaryJob', + 'getCurrentFileSummaryJob', + 'getFileSummaryJob', 'generateFileSummary', + 'getFileText', 'previewFile', 'previewS3FileGet', 'previewS3File', @@ -770,7 +894,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'integration-toolkit', title: 'Integration Toolkit API', serverUrl: 'https://integration-toolkit.sls.epilot.io', - operationCount: 51, + operationCount: 70, operationIds: [ 'acknowledgeTracking', 'triggerErp', @@ -778,6 +902,7 @@ export const API_LIST: ApiInfo[] = [ 'processErpUpdatesEventsV2', 'processErpUpdatesEventsV3', 'simulateMappingV2', + 'simulateDirect', 'simulateMapping', 'listIntegrations', 'createIntegration', @@ -792,11 +917,18 @@ export const API_LIST: ApiInfo[] = [ 'updateUseCase', 'deleteUseCase', 'listUseCaseHistory', + 'listDocumentationPages', + 'getDocumentationPage', + 'upsertDocumentationPage', + 'deleteDocumentationPage', 'listIntegrationsV2', 'createIntegrationV2', 'getIntegrationV2', 'updateIntegrationV2', 'deleteIntegrationV2', + 'listNotificationHistory', + 'testSendNotification', + 'getNotificationStatus', 'getSecureProxyWhitelist', 'updateSecureProxyWhitelist', 'listSecureProxyWhitelistHistory', @@ -806,6 +938,7 @@ export const API_LIST: ApiInfo[] = [ 'getMonitoringStats', 'getMonitoringTimeSeries', 'getOutboundStatus', + 'getEntitySyncStatus', 'pollOutboundMessages', 'ackOutboundMessages', 'listOutboundDlqMessages', @@ -817,12 +950,22 @@ export const API_LIST: ApiInfo[] = [ 'getMonitoringStatsV2', 'getMonitoringTimeSeriesV2', 'getAssociatedMonitoringEvents', + 'ingestExternalMonitoringEvents', + 'getMonitoringTraceByCorrelation', 'listSecureProxies', 'secureProxy', 'managedCallExecute', 'generateTypesPreview', 'generateTypes', 'commitTypes', + 'listErpImports', + 'createErpImport', + 'getErpImport', + 'deleteErpImport', + 'validateErpImport', + 'suggestErpImportUseCases', + 'executeErpImport', + 'abortErpImport', ], }, { @@ -830,11 +973,13 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'journey', title: 'Journey API', serverUrl: 'https://journey-config.sls.epilot.io', - operationCount: 16, + operationCount: 18, operationIds: [ 'getJourneysByOrgId', 'getJourney', 'removeJourney', + 'getJourneyEnvironment', + 'getJourneyEnvironmentVariables', 'getJourneyProducts', 'createJourney', 'updateJourney', @@ -874,7 +1019,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'message', title: 'Message API', serverUrl: 'https://message.sls.epilot.io', - operationCount: 52, + operationCount: 54, operationIds: [ 'sendMessage', 'updateMessage', @@ -888,10 +1033,12 @@ export const API_LIST: ApiInfo[] = [ 'markReadMessageV2', 'markUnreadMessage', 'getUnread', + 'getUnreadCounts', 'markUnreadMessageV2', 'searchThreads', 'searchThreadsV2', 'searchIds', + 'getAssigneeWorkload', 'updateThread', 'deleteThread', 'moveThread', @@ -934,8 +1081,8 @@ export const API_LIST: ApiInfo[] = [ apiName: 'metering', kebabName: 'metering', title: 'Metering API', - serverUrl: '', - operationCount: 20, + serverUrl: 'https://metering.sls.epilot.io', + operationCount: 21, operationIds: [ 'getCustomerMeters', 'getMetersByContractId', @@ -947,6 +1094,7 @@ export const API_LIST: ApiInfo[] = [ 'createMeterReadings', 'createPortalMeterReadings', 'batchWriteMeterReadings', + 'pruneMeterReadings', 'createMeterReadingFromSubmission', 'getAllowedReadingForMeter', 'createReadingWithMeter', @@ -1052,7 +1200,7 @@ export const API_LIST: ApiInfo[] = [ apiName: 'permissions', kebabName: 'permissions', title: 'Permissions API', - serverUrl: '', + serverUrl: 'https://permissions.sls.epilot.io', operationCount: 13, operationIds: [ 'listCurrentRoles', @@ -1075,7 +1223,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'pricing', title: 'Pricing API', serverUrl: 'https://pricing-api.sls.epilot.io', - operationCount: 22, + operationCount: 34, operationIds: [ '$calculatePricingDetails', 'createOrder', @@ -1099,6 +1247,18 @@ export const API_LIST: ApiInfo[] = [ '$getExternalCatalogProducts', '$getExternalCatalogProductRecommendations', '$productRecommendations', + '$getConditionSets', + '$resolveConditionalEntity', + '$createConditionalVariant', + '$getActiveConditionalVariantVersion', + '$replaceActiveConditionalVariantVersion', + '$patchActiveConditionalVariantVersion', + '$deleteConditionalVariant', + '$appendConditionalVariantVersion', + '$getConditionalVariantVersion', + '$replaceConditionalVariantVersion', + '$patchConditionalVariantVersion', + '$deleteConditionalVariantVersion', ], }, { @@ -1196,7 +1356,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'snapshot', title: 'Snapshot API', serverUrl: 'https://snapshot.sls.epilot.io', - operationCount: 9, + operationCount: 12, operationIds: [ 'listSnapshots', 'createSnapshot', @@ -1206,6 +1366,9 @@ export const API_LIST: ApiInfo[] = [ 'restoreSnapshot', 'listSnapshotResources', 'getSnapshotResource', + 'getOrgSnapshotSchedule', + 'putOrgSnapshotSchedule', + 'deleteOrgSnapshotSchedule', 'listDependencies', ], }, @@ -1222,7 +1385,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'targeting', title: 'Targeting API', serverUrl: 'https://targeting.sls.epilot.io', - operationCount: 12, + operationCount: 16, operationIds: [ 'changeCampaignStatus', 'getCampaignJobStatus', @@ -1230,12 +1393,16 @@ export const API_LIST: ApiInfo[] = [ 'retriggerCampaignAutomations', 'setupCampaign', 'matchCampaigns', + 'discoverCampaigns', 'matchTargets', 'getTargetQueries', 'createRecipient', 'updateRecipient', 'updateRecipientPortalStatus', + 'updateRecipientEntityUiStatus', + 'restoreRecipientEntityUiStatus', 'getRecipients', + 'getEmailStats', ], }, { @@ -1264,7 +1431,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'user', title: 'User API', serverUrl: 'https://user.sls.epilot.io', - operationCount: 41, + operationCount: 42, operationIds: [ 'signUpUser', 'getMeV2', @@ -1291,6 +1458,7 @@ export const API_LIST: ApiInfo[] = [ 'getNavigation', 'verifyEmailWithToken', 'requestPasswordReset', + 'resetPassword', 'checkInviteToken', 'activateUser', 'rejectInvite', @@ -1354,7 +1522,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'workflow', title: 'Workflows Executions', serverUrl: 'https://workflows-execution.sls.epilot.io', - operationCount: 24, + operationCount: 25, operationIds: [ 'getExecutions', 'createExecution', @@ -1378,6 +1546,7 @@ export const API_LIST: ApiInfo[] = [ 'patchPhase', 'addTask', 'cancelTaskSchedule', + 'reconcileAutomationTask', 'runTaskScheduleNow', 'cancelSchedule', ], diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 5c2854d21..181206a07 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -3,7 +3,7 @@ import { defineCommand } from 'citty'; export const main = defineCommand({ meta: { name: 'epilot', - version: '0.1.51', + version: '0.1.146', description: 'CLI for epilot APIs', }, args: { diff --git a/packages/epilot-sdk-v2/src/types/address-suggestions.d.ts b/packages/epilot-sdk-v2/src/types/address-suggestions.d.ts index fa2d5d1e8..4f6e58395 100644 --- a/packages/epilot-sdk-v2/src/types/address-suggestions.d.ts +++ b/packages/epilot-sdk-v2/src/types/address-suggestions.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from address-suggestions-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/address.d.ts b/packages/epilot-sdk-v2/src/types/address.d.ts index b43d1eb2a..67089bc21 100644 --- a/packages/epilot-sdk-v2/src/types/address.d.ts +++ b/packages/epilot-sdk-v2/src/types/address.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from address-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/audit-logs.d.ts b/packages/epilot-sdk-v2/src/types/audit-logs.d.ts index 61eebe72c..f053c518f 100644 --- a/packages/epilot-sdk-v2/src/types/audit-logs.d.ts +++ b/packages/epilot-sdk-v2/src/types/audit-logs.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from audit-logs-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, 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 df4e6dcb2..d4eaf6586 100644 --- a/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts +++ b/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts @@ -1248,307 +1248,307 @@ export declare namespace Paths { export interface OperationMethods { /** * listConfigTypes - listConfigTypes - * + * * Returns the static list of available configuration types with display metadata. * This is a cheap call — no fan-out to downstream APIs. Returns all known types * with labels and icons. The frontend should then call `listConfigs` separately * for each type it wants to load. - * + * */ 'listConfigTypes'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listConfigs - listConfigs - * + * * List configs of a given type with pagination. Returns summary metadata only * (not full payloads). The frontend calls this per type folder when expanding. - * + * * Supports offset-based pagination via `from` and `size` parameters. - * + * */ 'listConfigs'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getConfigDependencies - getConfigDependencies - * + * * Get configs that are referenced by the given config. * Used to render children when expanding a config node in the tree. - * + * * Resolves dependencies by fetching the config payload server-side and scanning * for references (UUIDs, source IDs, slug-based references). - * + * */ 'getConfigDependencies'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getConfigUsedBy - getConfigUsedBy - * + * * Get configs that reference the given config (reverse dependencies). * Scans the indexed config items for references to this config's ID or aliases. - * + * */ 'getConfigUsedBy'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getIndex - getIndex - * + * * Return the current index build state for the caller's organization. * Clients poll this to decide whether to show a "building" indicator * and when to refetch data. - * + * */ 'getIndex'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * compareConfigs - compareConfigs - * + * * Compare the caller org's configs of a single type against another * (source) org, side by side. Rows are paired via the lineage registry in * both sync directions (caller imported from source, or source imported * from caller); configs without a counterpart come back as `only_current` * or `only_source`. - * + * * `source_auth_token` must be a valid token for `source_org_id` — the * frontend mints one via the pipeline pairing, mirroring `createSyncJob`'s * `target_auth_token`. The token is verified against `source_org_id` * before any source-org data is read. - * + * * POST because the request carries a token; the operation reads only. - * + * */ 'compareConfigs'( parameters?: Parameters | null, data?: Paths.CompareConfigs.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * suggestMatches - suggestMatches - * + * * Run the sync-grade heuristic match (`lookupByHeuristic` — name / slug / * unique key) for a batch of source-org configs that have no lineage * entry, and return candidate counterparts in the caller's org. - * + * * Suggestions are ephemeral — nothing is persisted. The client offers * each candidate to the user, and a confirmed pair is written via * `confirmLineage`. Batches are capped at 25 ids; the client pages * through unmatched rows across successive calls. - * + * */ 'suggestMatches'( parameters?: Parameters | null, data?: Paths.SuggestMatches.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * confirmLineage - confirmLineage - * + * * Persist a lineage entry pairing a source-org config with a config in * the caller's org — used to confirm a heuristic suggestion from the * Compare view. Writes to the caller org's lineage partition (caller as * sync target), so subsequent syncs PATCH the confirmed target instead of * creating a duplicate. Conflicts (an existing entry pointing at a * different target) return 409 so the client can refresh. - * + * */ 'confirmLineage'( parameters?: Parameters | null, data?: Paths.ConfirmLineage.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * breakLineage - breakLineage - * + * * Delete a lineage entry from the caller org's partition (caller as sync * target), identified by `type` + `source_id`. Used to break a wrong or * stale match from the Compare view. Note: a future sync can re-match the * same pair heuristically — the entry is deleted, not blocklisted. To * break a match recorded in the OTHER direction, call this operation * authenticated as the other org (pipeline token). - * + * */ 'breakLineage'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listSyncJobs - listSyncJobs - * + * * List sync jobs scoped to the caller's organization, paginated with an opaque * cursor. Defaults to most-recent first. - * + * */ 'listSyncJobs'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * createSyncJob - createSyncJob - * + * * Create a new cross-org sync job. The job is enqueued for asynchronous execution * by the worker Lambda; the response returns the persisted job header with status * `pending`. - * + * * See `docs/sync/INTERFACES.md` for the locked request/response contract. - * + * */ 'createSyncJob'( parameters?: Parameters | null, data?: Paths.CreateSyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getSyncJob - getSyncJob - * + * * Fetch a single sync job by ID. Returns the job header, counts summary, * current phase pointer, and the latest activity events. Frontend polls this * endpoint with a ramping interval. - * + * */ 'getSyncJob'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * retrySyncJob - retrySyncJob - * + * * Retry the unresolved resources from a prior sync job: `failed` rows, plus * rows the original run left at `pending`/`in_progress` because it stopped * early. Creates a new job whose scope is that `(type, source_id)` set and * enqueues it for execution. Optionally accepts inline payload overrides. - * + * */ 'retrySyncJob'( parameters?: Parameters | null, data?: Paths.RetrySyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * cancelSyncJob - cancelSyncJob - * + * * Cancel a running sync job. Marks the job `cancelled` with a `finished_at` * so it stops being reported as in-flight, and the worker stops at its next * batch boundary — phases re-read the job status and abort rather than * overwrite a cancellation with their own outcome. - * + * * Resources already written to the target are NOT rolled back; cancelling * stops further work. Jobs already in a terminal state are rejected with * 409. - * + * */ 'cancelSyncJob'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listSyncJobResources - listSyncJobResources - * + * * List the per-resource rows for a sync job. Supports filtering by status * (e.g. `failed`) and cursor pagination. Used by the failures table and the * dry-run plan view in the frontend. - * + * */ 'listSyncJobResources'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listDeleteJobs - listDeleteJobs - * + * * List bulk-delete jobs scoped to the caller's organization, paginated * with an opaque cursor. Most-recent first. - * + * */ 'listDeleteJobs'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * createDeleteJob - createDeleteJob - * + * * Create a bulk-delete job for the caller's organization. The selected * resources are deleted asynchronously by a worker Lambda; the response * returns the persisted job header with status `pending`. - * + * * Raw delete — no dependency checks are performed. On completion the * worker auto-triggers an index rebuild so the config list reflects the * deletions. - * + * */ 'createDeleteJob'( parameters?: Parameters | null, data?: Paths.CreateDeleteJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getDeleteJob - getDeleteJob - * + * * Fetch a single bulk-delete job by ID. Returns the job header and counts * summary. Frontend polls this endpoint while the job runs. - * + * */ 'getDeleteJob'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listDeleteJobResources - listDeleteJobResources - * + * * List the per-resource rows for a delete job, cursor-paginated. Used by * the failures view in the frontend. - * + * */ 'listDeleteJobResources'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * rebuildIndex - rebuildIndex - * + * * Rebuild the configuration index for the caller's organization. * Fire-and-forget: invokes the async worker and returns immediately. * A new rebuild will cancel any in-flight build (see `build_token`). - * + * */ 'rebuildIndex'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * getConfigInventory - getConfigInventory - * + * * Returns a fresh inventory of an org's configuration resources — `{ type, id }` identities only, * no full payloads. Calls every adapter's `list()` live (bypasses the 7-day DynamoDB index). * Intended for snapshot-api to consume when taking a full-org snapshot. - * + * */ 'getConfigInventory'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } @@ -1556,341 +1556,341 @@ export interface PathsDictionary { ['/v1/configs/types']: { /** * listConfigTypes - listConfigTypes - * + * * Returns the static list of available configuration types with display metadata. * This is a cheap call — no fan-out to downstream APIs. Returns all known types * with labels and icons. The frontend should then call `listConfigs` separately * for each type it wants to load. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/{type}']: { /** * listConfigs - listConfigs - * + * * List configs of a given type with pagination. Returns summary metadata only * (not full payloads). The frontend calls this per type folder when expanding. - * + * * Supports offset-based pagination via `from` and `size` parameters. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/{type}/{id}/dependencies']: { /** * getConfigDependencies - getConfigDependencies - * + * * Get configs that are referenced by the given config. * Used to render children when expanding a config node in the tree. - * + * * Resolves dependencies by fetching the config payload server-side and scanning * for references (UUIDs, source IDs, slug-based references). - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/{type}/{id}/used_by']: { /** * getConfigUsedBy - getConfigUsedBy - * + * * Get configs that reference the given config (reverse dependencies). * Scans the indexed config items for references to this config's ID or aliases. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/index']: { /** * getIndex - getIndex - * + * * Return the current index build state for the caller's organization. * Clients poll this to decide whether to show a "building" indicator * and when to refetch data. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/compare']: { /** * compareConfigs - compareConfigs - * + * * Compare the caller org's configs of a single type against another * (source) org, side by side. Rows are paired via the lineage registry in * both sync directions (caller imported from source, or source imported * from caller); configs without a counterpart come back as `only_current` * or `only_source`. - * + * * `source_auth_token` must be a valid token for `source_org_id` — the * frontend mints one via the pipeline pairing, mirroring `createSyncJob`'s * `target_auth_token`. The token is verified against `source_org_id` * before any source-org data is read. - * + * * POST because the request carries a token; the operation reads only. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.CompareConfigs.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/compare/suggestions']: { /** * suggestMatches - suggestMatches - * + * * Run the sync-grade heuristic match (`lookupByHeuristic` — name / slug / * unique key) for a batch of source-org configs that have no lineage * entry, and return candidate counterparts in the caller's org. - * + * * Suggestions are ephemeral — nothing is persisted. The client offers * each candidate to the user, and a confirmed pair is written via * `confirmLineage`. Batches are capped at 25 ids; the client pages * through unmatched rows across successive calls. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.SuggestMatches.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/lineage']: { /** * confirmLineage - confirmLineage - * + * * Persist a lineage entry pairing a source-org config with a config in * the caller's org — used to confirm a heuristic suggestion from the * Compare view. Writes to the caller org's lineage partition (caller as * sync target), so subsequent syncs PATCH the confirmed target instead of * creating a duplicate. Conflicts (an existing entry pointing at a * different target) return 409 so the client can refresh. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.ConfirmLineage.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * breakLineage - breakLineage - * + * * Delete a lineage entry from the caller org's partition (caller as sync * target), identified by `type` + `source_id`. Used to break a wrong or * stale match from the Compare view. Note: a future sync can re-match the * same pair heuristically — the entry is deleted, not blocklisted. To * break a match recorded in the OTHER direction, call this operation * authenticated as the other org (pipeline token). - * + * */ 'delete'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs']: { /** * createSyncJob - createSyncJob - * + * * Create a new cross-org sync job. The job is enqueued for asynchronous execution * by the worker Lambda; the response returns the persisted job header with status * `pending`. - * + * * See `docs/sync/INTERFACES.md` for the locked request/response contract. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.CreateSyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listSyncJobs - listSyncJobs - * + * * List sync jobs scoped to the caller's organization, paginated with an opaque * cursor. Defaults to most-recent first. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}']: { /** * getSyncJob - getSyncJob - * + * * Fetch a single sync job by ID. Returns the job header, counts summary, * current phase pointer, and the latest activity events. Frontend polls this * endpoint with a ramping interval. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}/retry']: { /** * retrySyncJob - retrySyncJob - * + * * Retry the unresolved resources from a prior sync job: `failed` rows, plus * rows the original run left at `pending`/`in_progress` because it stopped * early. Creates a new job whose scope is that `(type, source_id)` set and * enqueues it for execution. Optionally accepts inline payload overrides. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.RetrySyncJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}/cancel']: { /** * cancelSyncJob - cancelSyncJob - * + * * Cancel a running sync job. Marks the job `cancelled` with a `finished_at` * so it stops being reported as in-flight, and the worker stops at its next * batch boundary — phases re-read the job status and abort rather than * overwrite a cancellation with their own outcome. - * + * * Resources already written to the target are NOT rolled back; cancelling * stops further work. Jobs already in a terminal state are rejected with * 409. - * + * */ 'post'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/sync-jobs/{id}/resources']: { /** * listSyncJobResources - listSyncJobResources - * + * * List the per-resource rows for a sync job. Supports filtering by status * (e.g. `failed`) and cursor pagination. Used by the failures table and the * dry-run plan view in the frontend. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/delete-jobs']: { /** * createDeleteJob - createDeleteJob - * + * * Create a bulk-delete job for the caller's organization. The selected * resources are deleted asynchronously by a worker Lambda; the response * returns the persisted job header with status `pending`. - * + * * Raw delete — no dependency checks are performed. On completion the * worker auto-triggers an index rebuild so the config list reflects the * deletions. - * + * */ 'post'( parameters?: Parameters | null, data?: Paths.CreateDeleteJob.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * listDeleteJobs - listDeleteJobs - * + * * List bulk-delete jobs scoped to the caller's organization, paginated * with an opaque cursor. Most-recent first. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/delete-jobs/{id}']: { /** * getDeleteJob - getDeleteJob - * + * * Fetch a single bulk-delete job by ID. Returns the job header and counts * summary. Frontend polls this endpoint while the job runs. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/delete-jobs/{id}/resources']: { /** * listDeleteJobResources - listDeleteJobResources - * + * * List the per-resource rows for a delete job, cursor-paginated. Used by * the failures view in the frontend. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/index:rebuild']: { /** * rebuildIndex - rebuildIndex - * + * * Rebuild the configuration index for the caller's organization. * Fire-and-forget: invokes the async worker and returns immediately. * A new rebuild will cancel any in-flight build (see `build_token`). - * + * */ 'post'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/configs/inventory']: { /** * getConfigInventory - getConfigInventory - * + * * Returns a fresh inventory of an org's configuration resources — `{ type, id }` identities only, * no full payloads. Calls every adapter's `list()` live (bypasses the 7-day DynamoDB index). * Intended for snapshot-api to consume when taking a full-org snapshot. - * + * */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } } diff --git a/packages/epilot-sdk-v2/src/types/consent.d.ts b/packages/epilot-sdk-v2/src/types/consent.d.ts index 247e2285e..0f740e840 100644 --- a/packages/epilot-sdk-v2/src/types/consent.d.ts +++ b/packages/epilot-sdk-v2/src/types/consent.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from consent-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/document.d.ts b/packages/epilot-sdk-v2/src/types/document.d.ts index 7783ff122..3b6c194af 100644 --- a/packages/epilot-sdk-v2/src/types/document.d.ts +++ b/packages/epilot-sdk-v2/src/types/document.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from document-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/email-template.d.ts b/packages/epilot-sdk-v2/src/types/email-template.d.ts index 2e6e6f294..4226b0834 100644 --- a/packages/epilot-sdk-v2/src/types/email-template.d.ts +++ b/packages/epilot-sdk-v2/src/types/email-template.d.ts @@ -993,6 +993,7 @@ export declare namespace Paths { } } + export interface OperationMethods { /** * saveTemplate - saveTemplate @@ -1159,6 +1160,7 @@ export interface PathsDictionary { export type Client = OpenAPIClient + export type ApproveAction = Components.Schemas.ApproveAction; export type ApprovedAt = Components.Schemas.ApprovedAt; export type AsyncEmailTemplateResponse = Components.Schemas.AsyncEmailTemplateResponse; diff --git a/packages/epilot-sdk-v2/src/types/file.d.ts b/packages/epilot-sdk-v2/src/types/file.d.ts index eb7f68624..7a4e21756 100644 --- a/packages/epilot-sdk-v2/src/types/file.d.ts +++ b/packages/epilot-sdk-v2/src/types/file.d.ts @@ -5375,23 +5375,23 @@ export interface OperationMethods { ): OperationResponse /** * getFileSummaryFeedback - Get file summary feedback - * + * * Get the authenticated user's feedback for the current generated file summary. */ 'getFileSummaryFeedback'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * putFileSummaryFeedback - Submit file summary feedback - * + * * Upsert thumbs up/down feedback for the current generated file summary. */ 'putFileSummaryFeedback'( parameters?: Parameters | null, data?: Paths.PutFileSummaryFeedback.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * createFileSummaryJob - createFileSummaryJob @@ -5918,23 +5918,23 @@ export interface PathsDictionary { ['/v1/files/{id}/summary/feedback']: { /** * getFileSummaryFeedback - Get file summary feedback - * + * * Get the authenticated user's feedback for the current generated file summary. */ 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse /** * putFileSummaryFeedback - Submit file summary feedback - * + * * Upsert thumbs up/down feedback for the current generated file summary. */ 'put'( parameters?: Parameters | null, data?: Paths.PutFileSummaryFeedback.RequestBody, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } ['/v1/files/{id}/summary-jobs']: { diff --git a/packages/epilot-sdk-v2/src/types/iban.d.ts b/packages/epilot-sdk-v2/src/types/iban.d.ts index 9a8069800..b7cc4b5fd 100644 --- a/packages/epilot-sdk-v2/src/types/iban.d.ts +++ b/packages/epilot-sdk-v2/src/types/iban.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from iban-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/notes.d.ts b/packages/epilot-sdk-v2/src/types/notes.d.ts index d05b7ab05..f1e4b4083 100644 --- a/packages/epilot-sdk-v2/src/types/notes.d.ts +++ b/packages/epilot-sdk-v2/src/types/notes.d.ts @@ -837,6 +837,7 @@ export declare namespace Paths { } } + export interface OperationMethods { /** * createNote - createNote @@ -1149,6 +1150,7 @@ export interface PathsDictionary { export type Client = OpenAPIClient + export type ContextType = Components.Schemas.ContextType; export type CreatedByType = Components.Schemas.CreatedByType; export type Entity = Components.Schemas.Entity; diff --git a/packages/epilot-sdk-v2/src/types/pricing-tier.d.ts b/packages/epilot-sdk-v2/src/types/pricing-tier.d.ts index d7bbb89ef..4f83aa584 100644 --- a/packages/epilot-sdk-v2/src/types/pricing-tier.d.ts +++ b/packages/epilot-sdk-v2/src/types/pricing-tier.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from pricing-tier-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/purpose.d.ts b/packages/epilot-sdk-v2/src/types/purpose.d.ts index d29ab3f04..7d6880f1d 100644 --- a/packages/epilot-sdk-v2/src/types/purpose.d.ts +++ b/packages/epilot-sdk-v2/src/types/purpose.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from purpose-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/sandbox.d.ts b/packages/epilot-sdk-v2/src/types/sandbox.d.ts index 979b1ed6e..f4bc1f29f 100644 --- a/packages/epilot-sdk-v2/src/types/sandbox.d.ts +++ b/packages/epilot-sdk-v2/src/types/sandbox.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from sandbox-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters, diff --git a/packages/epilot-sdk-v2/src/types/submission.d.ts b/packages/epilot-sdk-v2/src/types/submission.d.ts index 9c80d500e..674027f1f 100644 --- a/packages/epilot-sdk-v2/src/types/submission.d.ts +++ b/packages/epilot-sdk-v2/src/types/submission.d.ts @@ -1,6 +1,4 @@ /* Auto-copied from submission-client */ -/* eslint-disable */ - import type { OpenAPIClient, Parameters,