diff --git a/clients/design-client/package.json b/clients/design-client/package.json index fd3ac8749..ed2c9ba31 100644 --- a/clients/design-client/package.json +++ b/clients/design-client/package.json @@ -1,6 +1,6 @@ { "name": "@epilot/design-client", - "version": "0.5.7", + "version": "0.6.0", "description": "Client for epilot Design API", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/clients/design-client/src/openapi-runtime.json b/clients/design-client/src/openapi-runtime.json index eec01c16f..8062f742b 100644 --- a/clients/design-client/src/openapi-runtime.json +++ b/clients/design-client/src/openapi-runtime.json @@ -56,6 +56,11 @@ "in": "path", "name": "designId", "required": true + }, + { + "in": "header", + "name": "If-Match", + "required": false } ], "requestBody": { @@ -83,6 +88,7 @@ "/v1/designs/{designId}/parse": { "get": { "operationId": "getThemeFromDesign", + "deprecated": true, "parameters": [ { "in": "path", @@ -103,6 +109,30 @@ "responses": {} } }, + "/v1/designs/generate": { + "post": { + "operationId": "generateDesign", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/designs/document/generate": { + "post": { + "operationId": "generateDesignDocument", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, "/v1/designs/files": { "post": { "operationId": "uploadFile", @@ -132,20 +162,6 @@ "responses": {} } }, - "/v1/designs/limit": { - "get": { - "deprecated": true, - "operationId": "getLimit", - "responses": {} - } - }, - "/v1/brands": { - "get": { - "deprecated": true, - "operationId": "getBrands", - "responses": {} - } - }, "/v1/designs/consumer/{application}/{consumerId}": { "get": { "operationId": "getConsumerDesign", @@ -167,6 +183,7 @@ "/v1/designs/addConsumer/{application}/{designId}": { "put": { "operationId": "addConsumer", + "deprecated": true, "parameters": [ { "in": "path", @@ -191,6 +208,7 @@ "/v1/designs/removeConsumer/{application}/{designId}": { "put": { "operationId": "removeConsumer", + "deprecated": true, "parameters": [ { "in": "path", diff --git a/clients/design-client/src/openapi.d.ts b/clients/design-client/src/openapi.d.ts index d217a15f7..bb04639a3 100644 --- a/clients/design-client/src/openapi.d.ts +++ b/clients/design-client/src/openapi.d.ts @@ -16,9 +16,11 @@ declare namespace Components { */ DesignId; consumer_name: string; - should_delete?: string; } export interface AddDesignReq { + /** + * Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet. + */ design: { brand_id?: string; brand_name?: string; @@ -279,9 +281,29 @@ declare namespace Components { */ chip_hover_text_color?: string; }; + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + consumer?: ConsumerData; }; } export interface AddDesignRes { + /** + * Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet. + */ design?: { id?: string; /** @@ -552,6 +574,23 @@ declare namespace Components { */ chip_hover_text_color?: string; }; + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + consumer?: ConsumerData; }; } /** @@ -560,17 +599,6 @@ declare namespace Components { * journey */ export type Application = string; - export interface BrandItem { - id: string; - name: string; - created_by?: string; - created_date?: string; - main_brand?: string; - organization_id?: string; - updated_date?: string; - updated_by?: string; - status?: string; - } export interface ConsumerData { widgets: (WidgetData)[]; customer_portals: (CustomerPortalData)[]; @@ -583,6 +611,33 @@ declare namespace Components { id: string; name: string; } + /** + * Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet. + */ + export interface DesignDocumentV1 { + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + consumer?: ConsumerData; + } + export interface DesignFinding { + id: FindingId; + status: FindingStatus; + detail?: FindingDetail; + } /** * Id of the design * example: @@ -634,6 +689,10 @@ declare namespace Components { } export interface ErrorResp { message?: string; + /** + * Machine-readable reason, e.g. `invalid_url` or `rate_limited` + */ + code?: string; error?: { [key: string]: any; }; @@ -649,6 +708,14 @@ declare namespace Components { */ file_id?: string; } + export interface FindingDetail { + /** + * Font family that was found but cannot be loaded + */ + family: string; + } + export type FindingId = "primary_color" | "fonts" | "corners" | "primary_button" | "logo" | "favicon"; + export type FindingStatus = "found" | "not_found" | "not_available"; export interface FontData { font_id: string; font_name: string; @@ -662,6 +729,68 @@ declare namespace Components { type?: "WOFF2" | "WOFF" | "TTF" | "EOT"; url?: string; } + export interface GenerateDesignDocumentReq { + /** + * Public website to read the design from + * example: + * https://www.stadtwerke-musterstadt.de + */ + url: string; // uri + } + export interface GenerateDesignDocumentRes { + document: /* Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet. */ DesignDocumentV1; + findings: [ + DesignFinding, + DesignFinding, + DesignFinding, + DesignFinding, + DesignFinding, + DesignFinding + ]; + /** + * example: + * Stadtwerke Musterstadt + */ + site_name: string; + /** + * True when the response was served from an earlier successful generation for this organization, normalised URL and generator version + */ + cached: boolean; + } + export interface GenerateDesignReq { + /** + * Public website to derive the design from + * example: + * https://www.twl.de + */ + url: string; // uri + } + export interface GenerateDesignRes { + design: /* Partial design to merge into the design being edited */ GeneratedDesign; + /** + * True when the design was generated earlier for this organization and URL + */ + cached: boolean; + } + /** + * Partial design to merge into the design being edited + */ + export interface GeneratedDesign { + style: { + logo: LogoData; + palette: PaletteData; + typography: TypographyData; + }; + theme: { + shape: { + /** + * example: + * 8 + */ + borderRadius: string; + }; + }; + } export interface GetAllDesignsRes { designs?: { id?: string; @@ -933,22 +1062,29 @@ declare namespace Components { */ chip_hover_text_color?: string; }; - }[]; - } - export interface GetBrandsRes { - brands?: { - id: string; - name: string; - created_by?: string; - created_date?: string; - main_brand?: string; - organization_id?: string; - updated_date?: string; - updated_by?: string; - status?: string; + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + consumer?: ConsumerData; }[]; } export interface GetDesignRes { + /** + * Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet. + */ design?: { id?: string; /** @@ -1219,6 +1355,23 @@ declare namespace Components { */ chip_hover_text_color?: string; }; + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + consumer?: ConsumerData; }; } export type GetFilesRes = { @@ -1518,6 +1671,28 @@ declare namespace Components { journey_design_tokens?: { [key: string]: any; }; + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + /** + * v1 designs only: the journey group of @epilot/design-document (custom_css, tokens and the journey overrides primary_color, font, border_radius, logo), derived on read from the stored journey fields and compared with the stored v1 groups. What a journey paints from it equals what it paints from the theme built from the old fields. Not returned by GET. + */ + journey?: { + [name: string]: any; + }; }; } export interface RemoveConsumerReq { @@ -1527,7 +1702,6 @@ declare namespace Components { * 4a062990-a6a3-11eb-9828-4f3da7d4935a */ DesignId; - should_delete?: string; } export interface ShapeData { border_radius?: number; @@ -1543,7 +1717,304 @@ declare namespace Components { primary: string; secondary: string; } + export interface UpdateDesignConflict { + message?: string; + /** + * Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet. + */ + design?: { + id?: string; + /** + * Creation date and time + * example: + * 2021-01-30T08:30:00Z + */ + created_at?: string; + created_by?: string; + edited: boolean; + last_modified_at?: string; + brand_id?: string; + brand_name?: string; + user?: { + emailaddress?: string; + fullname?: string; + name?: string; + userid?: string; + }; + style_name: string; + style: { + logo?: LogoData; + palette: PaletteData; + typography: TypographyData; + shape?: ShapeData; + consumer: ConsumerData; + }; + is_default?: boolean; + /** + * The manifest IDs associated with this design + */ + _manifest?: string[]; + custom_theme?: string; + use_custom_theme?: boolean; + /** + * Design tokens for journey customization + */ + design_tokens?: { + coupon?: string; + cashback?: string; + custom_css?: string; + /** + * Accent color, defaults to primary + */ + accent_color?: string; + /** + * Global outline/focus color + */ + outline_color?: string; + /** + * Divider line color + */ + divider_color?: string; + /** + * Link text color + */ + link_color?: string; + /** + * Link hover text color + */ + link_hover_color?: string; + /** + * Font size scale factor + */ + font_size_scale?: "xs" | "sm" | "md" | "lg" | "xl"; + /** + * Topbar minimum height in pixels + */ + topbar_height?: number; + /** + * Logo/content alignment in the top bar + */ + topbar_logo_alignment?: "flex-start" | "center" | "flex-end"; + /** + * Logo size in pixels + */ + logo_size?: number; + /** + * Input field background color + */ + input_background?: string; + /** + * Input field border color + */ + input_border_color?: string; + /** + * Input field text color + */ + input_text_color?: string; + /** + * Input field label color + */ + input_label_color?: string; + /** + * Input field border radius in pixels + */ + input_border_radius?: number; + /** + * Input field height in pixels + */ + input_height?: number; + /** + * Input field variant style + */ + input_variant?: "outlined" | "filled" | "underlined"; + /** + * Primary button background color or gradient + */ + button_primary_bg?: string; + /** + * Primary button text color + */ + button_primary_text?: string; + /** + * Primary button hover background color or gradient + */ + button_primary_hover_bg?: string; + /** + * Primary button hover text color + */ + button_primary_hover_text?: string; + /** + * Outlined button border color + */ + button_outlined_border?: string; + /** + * Outlined button text color + */ + button_outlined_text?: string; + /** + * Outlined button hover background color + */ + button_outlined_hover_bg?: string; + /** + * Outlined button hover text color + */ + button_outlined_hover_text?: string; + /** + * Ghost button background color + */ + button_ghost_bg?: string; + /** + * Ghost button text color + */ + button_ghost_text?: string; + /** + * Ghost button hover background color + */ + button_ghost_hover_bg?: string; + /** + * Ghost button hover text color + */ + button_ghost_hover_text?: string; + /** + * Button border radius in pixels + */ + button_border_radius?: number; + /** + * Button height in pixels + */ + button_height?: number; + /** + * Card background color + */ + card_background?: string; + /** + * Card border color for outlined variant + */ + card_border_color?: string; + /** + * Card visual variant + */ + card_variant?: "shadow" | "outlined"; + /** + * Summary card background color + */ + summary_card_background?: string; + /** + * Toggle button selected background color + */ + toggle_selected_bg?: string; + /** + * Toggle button selected text color + */ + toggle_selected_text?: string; + /** + * Toggle button hover background color + */ + toggle_hover_bg?: string; + /** + * Toggle button hover text color + */ + toggle_hover_text?: string; + /** + * Toggle group wrapper border color + */ + toggle_border_color?: string; + /** + * Dropdown option hover background color + */ + dropdown_hover_bg?: string; + /** + * Dropdown option hover text color + */ + dropdown_hover_text?: string; + /** + * Dropdown option selected background color + */ + dropdown_selected_bg?: string; + /** + * Dropdown option selected text color + */ + dropdown_selected_text?: string; + /** + * Switch unchecked thumb color + */ + switch_unchecked_color?: string; + /** + * Switch unchecked track background color + */ + switch_unchecked_bg?: string; + /** + * Switch border radius in pixels + */ + switch_border_radius?: number; + /** + * Checkbox unchecked border color + */ + checkbox_unchecked_color?: string; + /** + * Checkbox label text color + */ + checkbox_label_color?: string; + /** + * Radio button unchecked border color + */ + radio_unchecked_color?: string; + /** + * Radio button label text color + */ + radio_label_color?: string; + /** + * Date picker selected date background color + */ + datepicker_selected_bg?: string; + /** + * Date picker selected date text color + */ + datepicker_selected_color?: string; + /** + * Date picker border radius in pixels + */ + datepicker_border_radius?: number; + /** + * Chip background color + */ + chip_background?: string; + /** + * Chip hover background color + */ + chip_hover_background?: string; + /** + * Chip text color + */ + chip_text_color?: string; + /** + * Chip hover text color + */ + chip_hover_text_color?: string; + }; + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + consumer?: ConsumerData; + }; + } export interface UpdateDesignReq { + /** + * Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet. + */ design: { brand_id?: string; brand_name?: string; @@ -1804,6 +2275,23 @@ declare namespace Components { */ chip_hover_text_color?: string; }; + version?: 1; + colors?: { + [name: string]: any; + }; + typography?: { + [name: string]: any; + }; + shape?: { + [name: string]: any; + }; + components?: { + [name: string]: any; + }; + assets?: { + [name: string]: any; + }; + consumer?: ConsumerData; }; } export interface UploadFileReq { @@ -1887,7 +2375,9 @@ declare namespace Paths { export type $401 = Components.Schemas.ErrorResp; export interface $404 { } + export type $409 = Components.Schemas.ErrorResp; export type $500 = Components.Schemas.ErrorResp; + export type $503 = Components.Schemas.ErrorResp; } } namespace DuplicateDesign { @@ -1911,15 +2401,36 @@ declare namespace Paths { export type $500 = Components.Schemas.ErrorResp; } } - namespace GetAllDesigns { + namespace GenerateDesign { + export type RequestBody = Components.Schemas.GenerateDesignReq; namespace Responses { - export type $200 = Components.Schemas.GetAllDesignsRes; - export type $500 = Components.Schemas.ErrorResp; + export type $200 = Components.Schemas.GenerateDesignRes; + export type $201 = Components.Schemas.GenerateDesignRes; + export type $400 = Components.Schemas.ErrorResp; + export type $401 = Components.Schemas.ErrorResp; + export type $403 = Components.Schemas.ErrorResp; + export type $422 = Components.Schemas.ErrorResp; + export type $502 = Components.Schemas.ErrorResp; + export type $504 = Components.Schemas.ErrorResp; } } - namespace GetBrands { + namespace GenerateDesignDocument { + export type RequestBody = Components.Schemas.GenerateDesignDocumentReq; namespace Responses { - export type $200 = Components.Schemas.GetBrandsRes[]; + export type $200 = Components.Schemas.GenerateDesignDocumentRes; + export type $201 = Components.Schemas.GenerateDesignDocumentRes; + export type $400 = Components.Schemas.ErrorResp; + export type $401 = Components.Schemas.ErrorResp; + export type $403 = Components.Schemas.ErrorResp; + export type $422 = Components.Schemas.ErrorResp; + export type $429 = Components.Schemas.ErrorResp; + export type $502 = Components.Schemas.ErrorResp; + export type $504 = Components.Schemas.ErrorResp; + } + } + namespace GetAllDesigns { + namespace Responses { + export type $200 = Components.Schemas.GetAllDesignsRes; export type $500 = Components.Schemas.ErrorResp; } } @@ -1986,12 +2497,6 @@ declare namespace Paths { export type $500 = Components.Schemas.ErrorResp; } } - namespace GetLimit { - namespace Responses { - export type $200 = number; - export type $500 = Components.Schemas.ErrorResp; - } - } namespace GetThemeFromDesign { namespace Parameters { export type DesignId = /** @@ -2050,6 +2555,9 @@ declare namespace Paths { } } namespace UpdateDesign { + export interface HeaderParameters { + "If-Match"?: Parameters.IfMatch; + } namespace Parameters { export type DesignId = /** * Id of the design @@ -2057,6 +2565,7 @@ declare namespace Paths { * 4a062990-a6a3-11eb-9828-4f3da7d4935a */ Components.Schemas.DesignId; + export type IfMatch = string; } export interface PathParameters { designId: Parameters.DesignId; @@ -2069,6 +2578,7 @@ declare namespace Paths { export type $401 = Components.Schemas.ErrorResp; export interface $404 { } + export type $409 = Components.Schemas.UpdateDesignConflict; export type $500 = Components.Schemas.ErrorResp; } } @@ -2118,10 +2628,10 @@ export interface OperationMethods { /** * updateDesign - updateDesign * - * Update a especific design owned by user organization + * Update a especific design owned by user organization. A body with `version: 1` is validated against the design document v1 schema and stored as v1. A v1 body leaves the stored `style`, `design_tokens`, `custom_theme` and `use_custom_theme` untouched. A body without `version` on a v1 design is treated as an old client's read-modify-write: those four fields are stored as sent, and brand-wide changes in `style` are applied to the v1 groups. */ 'updateDesign'( - parameters?: Parameters | null, + parameters?: Parameters | null, data?: Paths.UpdateDesign.RequestBody, config?: AxiosRequestConfig ): OperationResponse @@ -2148,13 +2658,42 @@ export interface OperationMethods { /** * getThemeFromDesign - getThemeFromDesign * - * Search for a especific design owned by user organization and parse them to a new or old theme + * Search for a especific design owned by user organization and parse them to a new or old theme. Deprecated: kept for old readers only. For a v1 design the response carries the v1 groups and the derived `journey` group next to the stored old fields and the theme built from them; new runtimes read the v1 groups. */ 'getThemeFromDesign'( parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * generateDesign - generateDesign + * + * Generates a design (palette, text colours, font, logo and border radius) from a public + * website with AI. The result is cached per organization and URL for 30 days. + * + */ + 'generateDesign'( + parameters?: Parameters | null, + data?: Paths.GenerateDesign.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * generateDesignDocument - generateDesignDocument + * + * Reads a public website and returns a v1 design document plus one finding for each + * id in fixed order. This version chooses the favicon in code and derives the site name. + * The other findings are `not_found` until they are implemented. The document contains + * only the leaves that passed validation, and defaults are not pruned. + * Successful responses are cached per organization, normalised URL and generator version + * for 7 days. Each organization may run 20 new generations per UTC day; cache hits do not + * count toward the limit. A client abort still finishes on the server and is cached. + * + */ + 'generateDesignDocument'( + parameters?: Parameters | null, + data?: Paths.GenerateDesignDocument.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse /** * getFiles - getFiles * @@ -2175,26 +2714,6 @@ export interface OperationMethods { data?: Paths.UploadFile.RequestBody, config?: AxiosRequestConfig ): OperationResponse - /** - * getLimit - getLimit - * - * Gets designs number limit from database per organization - */ - 'getLimit'( - parameters?: Parameters | null, - data?: any, - config?: AxiosRequestConfig - ): OperationResponse - /** - * getBrands - getBrands - * - * Scan all brands linked to a organization, based in orgId attribute from JWT auth token - */ - 'getBrands'( - parameters?: Parameters | null, - data?: any, - config?: AxiosRequestConfig - ): OperationResponse /** * getConsumerDesign - getConsumerDesign * @@ -2274,10 +2793,10 @@ export interface PathsDictionary { /** * updateDesign - updateDesign * - * Update a especific design owned by user organization + * Update a especific design owned by user organization. A body with `version: 1` is validated against the design document v1 schema and stored as v1. A v1 body leaves the stored `style`, `design_tokens`, `custom_theme` and `use_custom_theme` untouched. A body without `version` on a v1 design is treated as an old client's read-modify-write: those four fields are stored as sent, and brand-wide changes in `style` are applied to the v1 groups. */ 'put'( - parameters?: Parameters | null, + parameters?: Parameters | null, data?: Paths.UpdateDesign.RequestBody, config?: AxiosRequestConfig ): OperationResponse @@ -2298,7 +2817,7 @@ export interface PathsDictionary { /** * getThemeFromDesign - getThemeFromDesign * - * Search for a especific design owned by user organization and parse them to a new or old theme + * Search for a especific design owned by user organization and parse them to a new or old theme. Deprecated: kept for old readers only. For a v1 design the response carries the v1 groups and the derived `journey` group next to the stored old fields and the theme built from them; new runtimes read the v1 groups. */ 'get'( parameters?: Parameters | null, @@ -2306,51 +2825,60 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } - ['/v1/designs/files']: { + ['/v1/designs/generate']: { /** - * uploadFile - uploadFile + * generateDesign - generateDesign + * + * Generates a design (palette, text colours, font, logo and border radius) from a public + * website with AI. The result is cached per organization and URL for 30 days. * - * Upload a new file for the user organization bucket */ 'post'( parameters?: Parameters | null, - data?: Paths.UploadFile.RequestBody, + data?: Paths.GenerateDesign.RequestBody, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse + } + ['/v1/designs/document/generate']: { /** - * getFiles - getFiles + * generateDesignDocument - generateDesignDocument + * + * Reads a public website and returns a v1 design document plus one finding for each + * id in fixed order. This version chooses the favicon in code and derives the site name. + * The other findings are `not_found` until they are implemented. The document contains + * only the leaves that passed validation, and defaults are not pruned. + * Successful responses are cached per organization, normalised URL and generator version + * for 7 days. Each organization may run 20 new generations per UTC day; cache hits do not + * count toward the limit. A client abort still finishes on the server and is cached. * - * List all files for the user organization bucket */ - 'get'( - parameters?: Parameters | null, - data?: any, + 'post'( + parameters?: Parameters | null, + data?: Paths.GenerateDesignDocument.RequestBody, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse } - ['/v1/designs/limit']: { + ['/v1/designs/files']: { /** - * getLimit - getLimit + * uploadFile - uploadFile * - * Gets designs number limit from database per organization + * Upload a new file for the user organization bucket */ - 'get'( + 'post'( parameters?: Parameters | null, - data?: any, + data?: Paths.UploadFile.RequestBody, config?: AxiosRequestConfig - ): OperationResponse - } - ['/v1/brands']: { + ): OperationResponse /** - * getBrands - getBrands + * getFiles - getFiles * - * Scan all brands linked to a organization, based in orgId attribute from JWT auth token + * List all files for the user organization bucket */ 'get'( - parameters?: Parameters | null, + parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse } ['/v1/designs/consumer/{application}/{consumerId}']: { /** @@ -2397,19 +2925,27 @@ export type AddConsumerReq = Components.Schemas.AddConsumerReq; export type AddDesignReq = Components.Schemas.AddDesignReq; export type AddDesignRes = Components.Schemas.AddDesignRes; export type Application = Components.Schemas.Application; -export type BrandItem = Components.Schemas.BrandItem; export type ConsumerData = Components.Schemas.ConsumerData; export type Custom_Style = Components.Schemas.CustomStyle; export type CustomerPortalData = Components.Schemas.CustomerPortalData; +export type Design_Document_V1 = Components.Schemas.DesignDocumentV1; +export type DesignFinding = Components.Schemas.DesignFinding; export type DesignId = Components.Schemas.DesignId; export type DesignItem = Components.Schemas.DesignItem; export type DesignParameters = Components.Schemas.DesignParameters; export type ErrorResp = Components.Schemas.ErrorResp; export type FileData = Components.Schemas.FileData; +export type FindingDetail = Components.Schemas.FindingDetail; +export type FindingId = Components.Schemas.FindingId; +export type FindingStatus = Components.Schemas.FindingStatus; export type FontData = Components.Schemas.FontData; export type FontResponseUrl = Components.Schemas.FontResponseUrl; +export type GenerateDesignDocumentReq = Components.Schemas.GenerateDesignDocumentReq; +export type GenerateDesignDocumentRes = Components.Schemas.GenerateDesignDocumentRes; +export type GenerateDesignReq = Components.Schemas.GenerateDesignReq; +export type GenerateDesignRes = Components.Schemas.GenerateDesignRes; +export type GeneratedDesign = Components.Schemas.GeneratedDesign; export type GetAllDesignsRes = Components.Schemas.GetAllDesignsRes; -export type GetBrandsRes = Components.Schemas.GetBrandsRes; export type GetDesignRes = Components.Schemas.GetDesignRes; export type GetFilesRes = Components.Schemas.GetFilesRes; export type ItemMetada = Components.Schemas.ItemMetada; @@ -2421,6 +2957,7 @@ export type RemoveConsumerReq = Components.Schemas.RemoveConsumerReq; export type ShapeData = Components.Schemas.ShapeData; export type Theme = Components.Schemas.Theme; export type TypographyData = Components.Schemas.TypographyData; +export type UpdateDesignConflict = Components.Schemas.UpdateDesignConflict; export type UpdateDesignReq = Components.Schemas.UpdateDesignReq; export type UploadFileReq = Components.Schemas.UploadFileReq; export type UploadFileRes = Components.Schemas.UploadFileRes; diff --git a/clients/design-client/src/openapi.json b/clients/design-client/src/openapi.json index 2e4c25182..abf708a88 100644 --- a/clients/design-client/src/openapi.json +++ b/clients/design-client/src/openapi.json @@ -24,6 +24,11 @@ "/v1/designs": { "get": { "operationId": "getAllDesigns", + "x-epilot-permissions": [ + { + "action": "design:view" + } + ], "summary": "getAllDesigns", "description": "Scan all designs linked to a organization, based in orgId attribute from JWT auth token", "tags": [ @@ -54,6 +59,11 @@ }, "post": { "operationId": "addDesign", + "x-epilot-permissions": [ + { + "action": "design:create" + } + ], "summary": "addDesign", "description": "Create a brand new design linked to a organization, based in orgId attribute from JWT auth token", "tags": [ @@ -117,6 +127,11 @@ "/v1/designs/{designId}": { "get": { "operationId": "getDesign", + "x-epilot-permissions": [ + { + "action": "design:view" + } + ], "summary": "getDesign", "description": "Search for a especific design owned by user organization", "tags": [ @@ -180,6 +195,11 @@ }, "delete": { "operationId": "deleteDesign", + "x-epilot-permissions": [ + { + "action": "design:delete" + } + ], "summary": "deleteDesign", "description": "Search and delete for a especific design owned by user organization", "tags": [ @@ -222,6 +242,16 @@ "404": { "description": "No design found" }, + "409": { + "description": "The design cannot be deleted: it is the only one of the organization, it is still\nassigned to journeys (listed in `journeys`) or portals (listed in `portals`), or the\ncaller may not view the organization's portals, so their usage cannot be checked.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, "500": { "description": "Other errors", "content": { @@ -231,13 +261,28 @@ } } } + }, + "503": { + "description": "customer-portal-api could not be reached to check which portals use the design. Retry later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } } } }, "put": { "operationId": "updateDesign", + "x-epilot-permissions": [ + { + "action": "design:edit" + } + ], "summary": "updateDesign", - "description": "Update a especific design owned by user organization", + "description": "Update a especific design owned by user organization. A body with `version: 1` is validated against the design document v1 schema and stored as v1. A v1 body leaves the stored `style`, `design_tokens`, `custom_theme` and `use_custom_theme` untouched. A body without `version` on a v1 design is treated as an old client's read-modify-write: those four fields are stored as sent, and brand-wide changes in `style` are applied to the v1 groups.", "tags": [ "design-builder" ], @@ -249,6 +294,15 @@ "schema": { "$ref": "#/components/schemas/DesignId" } + }, + { + "in": "header", + "name": "If-Match", + "required": false, + "description": "The `last_modified_at` of the design the client read. When it no longer matches the stored design, the update is rejected with 409. Without the header the update is unconditional.", + "schema": { + "type": "string" + } } ], "requestBody": { @@ -289,6 +343,16 @@ "404": { "description": "No design found" }, + "409": { + "description": "The design was changed since the `If-Match` value was read. Returns the current design.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDesignConflict" + } + } + } + }, "500": { "description": "Other errors", "content": { @@ -305,6 +369,11 @@ "/v1/designs/{designId}/duplicate": { "post": { "operationId": "duplicateDesign", + "x-epilot-permissions": [ + { + "action": "design:create" + } + ], "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": [ @@ -371,7 +440,8 @@ "get": { "operationId": "getThemeFromDesign", "summary": "getThemeFromDesign", - "description": "Search for a especific design owned by user organization and parse them to a new or old theme", + "deprecated": true, + "description": "Search for a especific design owned by user organization and parse them to a new or old theme. Deprecated: kept for old readers only. For a v1 design the response carries the v1 groups and the derived `journey` group next to the stored old fields and the theme built from them; new runtimes read the v1 groups.", "security": [], "tags": [ "design-builder" @@ -450,43 +520,59 @@ } } }, - "/v1/designs/files": { + "/v1/designs/generate": { "post": { - "operationId": "uploadFile", - "summary": "uploadFile", - "description": "Upload a new file for the user organization bucket", + "operationId": "generateDesign", + "x-epilot-permissions": [ + { + "anyOf": [ + { + "action": "design:create" + }, + { + "action": "design:edit" + } + ] + } + ], + "summary": "generateDesign", + "description": "Generates a design (palette, text colours, font, logo and border radius) from a public\nwebsite with AI. The result is cached per organization and URL for 30 days.\n", "tags": [ "design-builder" ], "requestBody": { - "description": "Upload file payload", "required": true, "content": { - "multipart/form-data": { + "application/json": { "schema": { - "$ref": "#/components/schemas/UploadFileReq" - }, - "encoding": { - "file_data": { - "contentType": "image/*, application/x-font-ttf, application/x-font-opentype, application/font-woff, application/font-woff2" - } + "$ref": "#/components/schemas/GenerateDesignReq" } } } }, "responses": { + "200": { + "description": "A design generated earlier for this organization and URL", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateDesignRes" + } + } + } + }, "201": { - "description": "Success - upload with success.", + "description": "A newly generated design", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UploadFileRes" + "$ref": "#/components/schemas/GenerateDesignRes" } } } }, "400": { - "description": "Validation Errors", + "description": "The URL is not a valid http(s) URL (`invalid_url`) or points to a private network (`blocked_url`)", "content": { "application/json": { "schema": { @@ -505,8 +591,38 @@ } } }, - "500": { - "description": "Other errors", + "403": { + "description": "The caller may not edit designs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "422": { + "description": "The website could not be fetched (`fetch_failed`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "502": { + "description": "The design could not be generated (`generation_failed`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "504": { + "description": "The generation did not finish in time", "content": { "application/json": { "schema": { @@ -516,38 +632,161 @@ } } } - }, - "get": { - "operationId": "getFiles", - "summary": "getFiles", - "description": "List all files for the user organization bucket", + } + }, + "/v1/designs/document/generate": { + "post": { + "operationId": "generateDesignDocument", + "x-epilot-permissions": [ + { + "action": "design:create" + } + ], + "summary": "generateDesignDocument", + "description": "Reads a public website and returns a v1 design document plus one finding for each\nid in fixed order. This version chooses the favicon in code and derives the site name.\nThe other findings are `not_found` until they are implemented. The document contains\nonly the leaves that passed validation, and defaults are not pruned.\nSuccessful responses are cached per organization, normalised URL and generator version\nfor 7 days. Each organization may run 20 new generations per UTC day; cache hits do not\ncount toward the limit. A client abort still finishes on the server and is cached.\n", "tags": [ "design-builder" ], - "parameters": [ + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateDesignDocumentReq" + } + } + } + }, + "responses": { + "200": { + "description": "A design document generated earlier for this organization, URL and generator version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateDesignDocumentRes" + } + } + } + }, + "201": { + "description": "A newly generated design document", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateDesignDocumentRes" + } + } + } + }, + "400": { + "description": "The URL is not a valid http(s) URL (`invalid_url`) or points to a private network (`blocked_url`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "401": { + "description": "Authentication Errors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "403": { + "description": "The caller does not have design:create", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "422": { + "description": "The website could not be fetched (`fetch_failed`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "429": { + "description": "The organization has used today's generation limit (`rate_limited`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "502": { + "description": "Generation failed, or no finding survived (`generation_failed`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + }, + "504": { + "description": "The 27 second budget ran out (`timeout`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResp" + } + } + } + } + } + } + }, + "/v1/designs/files": { + "post": { + "operationId": "uploadFile", + "x-epilot-permissions": [ { - "in": "query", - "name": "type", - "schema": { - "type": "string", - "enum": [ - "LOGO", - "FONT", - "IMAGE" - ] - }, - "required": false, - "description": "Type of files to be returned", - "example": "LOGO" + "access": "org" } ], + "summary": "uploadFile", + "description": "Upload a new file for the user organization bucket", + "tags": [ + "design-builder" + ], + "requestBody": { + "description": "Upload file payload", + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/UploadFileReq" + }, + "encoding": { + "file_data": { + "contentType": "image/*, application/x-font-ttf, application/x-font-opentype, application/font-woff, application/font-woff2" + } + } + } + } + }, "responses": { - "200": { - "description": "Success - files loaded with success.", + "201": { + "description": "Success - upload with success.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetFilesRes" + "$ref": "#/components/schemas/UploadFileRes" } } } @@ -583,30 +822,49 @@ } } } - } - }, - "/v1/designs/limit": { + }, "get": { - "deprecated": true, - "operationId": "getLimit", - "summary": "getLimit", - "description": "Gets designs number limit from database per organization", + "operationId": "getFiles", + "x-epilot-permissions": [ + { + "access": "org" + } + ], + "summary": "getFiles", + "description": "List all files for the user organization bucket", "tags": [ "design-builder" ], + "parameters": [ + { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "LOGO", + "FONT", + "IMAGE" + ] + }, + "required": false, + "description": "Type of files to be returned", + "example": "LOGO" + } + ], "responses": { "200": { - "description": "Success - limit loaded with success.", + "description": "Success - files loaded with success.", "content": { "application/json": { "schema": { - "type": "number" + "$ref": "#/components/schemas/GetFilesRes" } } } }, - "500": { - "description": "Other errors", + "400": { + "description": "Validation Errors", "content": { "application/json": { "schema": { @@ -614,29 +872,13 @@ } } } - } - } - } - }, - "/v1/brands": { - "get": { - "deprecated": true, - "operationId": "getBrands", - "summary": "getBrands", - "description": "Scan all brands linked to a organization, based in orgId attribute from JWT auth token", - "tags": [ - "design-builder" - ], - "responses": { - "200": { - "description": "Success - brands loaded with success. Empty array if org has no designs.", + }, + "401": { + "description": "Authentication Errors", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GetBrandsRes" - } + "$ref": "#/components/schemas/ErrorResp" } } } @@ -657,6 +899,11 @@ "/v1/designs/consumer/{application}/{consumerId}": { "get": { "operationId": "getConsumerDesign", + "x-epilot-permissions": [ + { + "access": "org" + } + ], "summary": "getConsumerDesign", "description": "Search for a especific design owned by user organization", "tags": [ @@ -730,6 +977,30 @@ "/v1/designs/addConsumer/{application}/{designId}": { "put": { "operationId": "addConsumer", + "deprecated": true, + "x-epilot-permissions": [ + { + "anyOf": [ + { + "action": "entity:edit", + "resource": "journey" + }, + { + "action": "customer_portal:edit" + }, + { + "action": "installer_portal:edit" + }, + { + "action": "portal:content:edit", + "resource": "{consumer_id}" + }, + { + "action": "design:edit" + } + ] + } + ], "summary": "addConsumer", "description": "Add a consumer that uses a specific design", "tags": [ @@ -809,6 +1080,30 @@ "/v1/designs/removeConsumer/{application}/{designId}": { "put": { "operationId": "removeConsumer", + "deprecated": true, + "x-epilot-permissions": [ + { + "anyOf": [ + { + "action": "entity:edit", + "resource": "journey" + }, + { + "action": "customer_portal:edit" + }, + { + "action": "installer_portal:edit" + }, + { + "action": "portal:content:edit", + "resource": "{consumer_id}" + }, + { + "action": "design:edit" + } + ] + } + ], "summary": "removeConsumer", "description": "Remove a consumer that uses a specific design", "tags": [ @@ -959,21 +1254,9 @@ }, { "$ref": "#/components/schemas/Journey" - } - ] - } - } - } - }, - "GetBrandsRes": { - "type": "object", - "properties": { - "brands": { - "type": "array", - "items": { - "allOf": [ + }, { - "$ref": "#/components/schemas/BrandItem" + "$ref": "#/components/schemas/Design_Document_V1" } ] } @@ -993,6 +1276,9 @@ }, { "$ref": "#/components/schemas/Journey" + }, + { + "$ref": "#/components/schemas/Design_Document_V1" } ] } @@ -1017,6 +1303,9 @@ }, { "$ref": "#/components/schemas/Journey" + }, + { + "$ref": "#/components/schemas/Design_Document_V1" } ] } @@ -1038,6 +1327,9 @@ }, { "$ref": "#/components/schemas/Journey" + }, + { + "$ref": "#/components/schemas/Design_Document_V1" } ] } @@ -1056,6 +1348,9 @@ }, { "$ref": "#/components/schemas/Journey" + }, + { + "$ref": "#/components/schemas/Design_Document_V1" } ] } @@ -1094,9 +1389,6 @@ }, "consumer_name": { "type": "string" - }, - "should_delete": { - "type": "string" } }, "required": [ @@ -1109,10 +1401,6 @@ "properties": { "consumer_id": { "$ref": "#/components/schemas/DesignId" - }, - "should_delete": { - "type": "string", - "deprecated": true } }, "required": [ @@ -1196,41 +1484,67 @@ "style" ] }, - "BrandItem": { + "Design_Document_V1": { "type": "object", + "description": "Groups of the design document v1. A design is v1 when `version` is 1, otherwise it is the old shape (`style`, `design_tokens`, `custom_theme`). The authoritative contract for the groups is the schema exported by @epilot/design-document. A v1 design keeps `style`, `design_tokens`, `custom_theme` and `use_custom_theme` as stored. The `journey` group is not accepted yet.", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" + "version": { + "type": "integer", + "enum": [ + 1 + ] }, - "created_by": { - "type": "string" + "colors": { + "type": "object", + "additionalProperties": {} }, - "created_date": { - "type": "string" + "typography": { + "type": "object", + "additionalProperties": {} }, - "main_brand": { - "type": "string" + "shape": { + "type": "object", + "additionalProperties": {} }, - "organization_id": { - "type": "string" + "components": { + "type": "object", + "additionalProperties": {} }, - "updated_date": { - "type": "string" + "assets": { + "type": "object", + "additionalProperties": {} }, - "updated_by": { + "consumer": { + "$ref": "#/components/schemas/ConsumerData" + } + } + }, + "UpdateDesignConflict": { + "type": "object", + "properties": { + "message": { "type": "string" }, - "status": { - "type": "string" + "design": { + "allOf": [ + { + "$ref": "#/components/schemas/ItemMetada" + }, + { + "$ref": "#/components/schemas/DesignItem" + }, + { + "$ref": "#/components/schemas/Custom_Style" + }, + { + "$ref": "#/components/schemas/Journey" + }, + { + "$ref": "#/components/schemas/Design_Document_V1" + } + ] } - }, - "required": [ - "id", - "name" - ] + } }, "Custom_Style": { "type": "object", @@ -1696,12 +2010,188 @@ "name" ] }, + "GenerateDesignReq": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Public website to derive the design from", + "example": "https://www.twl.de" + } + }, + "required": [ + "url" + ] + }, + "GenerateDesignRes": { + "type": "object", + "properties": { + "design": { + "$ref": "#/components/schemas/GeneratedDesign" + }, + "cached": { + "type": "boolean", + "description": "True when the design was generated earlier for this organization and URL" + } + }, + "required": [ + "design", + "cached" + ] + }, + "GenerateDesignDocumentReq": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Public website to read the design from", + "example": "https://www.stadtwerke-musterstadt.de" + } + }, + "required": [ + "url" + ] + }, + "FindingId": { + "type": "string", + "enum": [ + "primary_color", + "fonts", + "corners", + "primary_button", + "logo", + "favicon" + ] + }, + "FindingStatus": { + "type": "string", + "enum": [ + "found", + "not_found", + "not_available" + ] + }, + "FindingDetail": { + "type": "object", + "properties": { + "family": { + "type": "string", + "description": "Font family that was found but cannot be loaded" + } + }, + "required": [ + "family" + ] + }, + "DesignFinding": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/FindingId" + }, + "status": { + "$ref": "#/components/schemas/FindingStatus" + }, + "detail": { + "$ref": "#/components/schemas/FindingDetail" + } + }, + "required": [ + "id", + "status" + ] + }, + "GenerateDesignDocumentRes": { + "type": "object", + "properties": { + "document": { + "$ref": "#/components/schemas/Design_Document_V1" + }, + "findings": { + "type": "array", + "minItems": 6, + "maxItems": 6, + "items": { + "$ref": "#/components/schemas/DesignFinding" + } + }, + "site_name": { + "type": "string", + "example": "Stadtwerke Musterstadt" + }, + "cached": { + "type": "boolean", + "description": "True when the response was served from an earlier successful generation for this organization, normalised URL and generator version" + } + }, + "required": [ + "document", + "findings", + "site_name", + "cached" + ] + }, + "GeneratedDesign": { + "type": "object", + "description": "Partial design to merge into the design being edited", + "properties": { + "style": { + "type": "object", + "properties": { + "logo": { + "$ref": "#/components/schemas/LogoData" + }, + "palette": { + "$ref": "#/components/schemas/PaletteData" + }, + "typography": { + "$ref": "#/components/schemas/TypographyData" + } + }, + "required": [ + "logo", + "palette", + "typography" + ] + }, + "theme": { + "type": "object", + "properties": { + "shape": { + "type": "object", + "properties": { + "borderRadius": { + "type": "string", + "example": "8" + } + }, + "required": [ + "borderRadius" + ] + } + }, + "required": [ + "shape" + ] + } + }, + "required": [ + "style", + "theme" + ] + }, "ErrorResp": { "type": "object", "properties": { "message": { "type": "string" }, + "code": { + "type": "string", + "description": "Machine-readable reason, e.g. `invalid_url` or `rate_limited`" + }, "error": { "type": "object" } @@ -1817,6 +2307,37 @@ "journey_design_tokens": { "type": "object", "description": "Design tokens for journey customization (renamed from design_tokens)" + }, + "version": { + "type": "integer", + "enum": [ + 1 + ] + }, + "colors": { + "type": "object", + "additionalProperties": {} + }, + "typography": { + "type": "object", + "additionalProperties": {} + }, + "shape": { + "type": "object", + "additionalProperties": {} + }, + "components": { + "type": "object", + "additionalProperties": {} + }, + "assets": { + "type": "object", + "additionalProperties": {} + }, + "journey": { + "type": "object", + "additionalProperties": {}, + "description": "v1 designs only: the journey group of @epilot/design-document (custom_css, tokens and the journey overrides primary_color, font, border_radius, logo), derived on read from the stored journey fields and compared with the stored v1 groups. What a journey paints from it equals what it paints from the theme built from the old fields. Not returned by GET." } } }