{"openapi":"3.1.0","info":{"title":"Remizo Hub API","version":"1.0.0","summary":"Management API of the Remizo hub, consumed by the Studio (client marketers) and the Console (editor staff).","description":"The hub is the control plane: workspaces, infrastructure, backups, cron, users, API keys, feature flags, and the **authoring** side of every workspace (offers, coupons, customers, segments, A/B tests, dashboards, audit, webhooks). Runtime calls (tickets, points) go to the workspace **engine** (see `engine.openapi.yaml`); the hub only proxies the simulator.\n\n**Authentication** — Keycloak bearer tokens (one Keycloak, one login URL):\n* realm `studio` for client users; the token's `organization` claim lists the workspaces (Keycloak Organizations, alias = workspace slug) the user belongs to. The user's **role inside a workspace** comes from `workspace_members` (hub DB), not from Keycloak.\n* realm `admin` for editor staff (MFA required); realm roles `platform_admin`, `platform_operator`, `platform_support`.\nEvery `/v1/workspaces/{ws}/...` call checks that `{ws}` is in the token's organizations (studio) or that the caller is staff (admin). The `X-Workspace` header is not used — the slug is always in the path.\n\n**Conventions**: integer cents; RFC 9457 `application/problem+json`; cursor pagination (`cursor`, `limit` ≤ 200, response `{ items, nextCursor }`); `Idempotency-Key` accepted on every POST that creates something; `If-Match` / `ETag` on offer drafts; all timestamps RFC 3339 UTC; `X-Request-Id` echoed.\n","contact":{"name":"Remizo","url":"https://remizo.dev"}},"servers":[{"url":"http://localhost:47010/v1","description":"Local hub API"},{"url":"https://hub.remizo.dev/v1","description":"Production pattern"}],"tags":[{"name":"Me","description":"Current user, workspaces, context switch."},{"name":"Workspaces","description":"Workspace lifecycle and provisioning (staff), settings, stores."},{"name":"Servers","description":"Deployment targets (staff)."},{"name":"Infra","description":"Per-workspace infrastructure status and actions (staff)."},{"name":"Backups","description":"Policies, runs, restores (staff; owners read)."},{"name":"Cron","description":"Scheduled jobs (staff; workspace view for owners)."},{"name":"Sync","description":"CRM synchronisation connectors."},{"name":"ApiKeys","description":"API keys for POS / Beone."},{"name":"Members","description":"Workspace users, roles, SSO, staff."},{"name":"Features","description":"Feature flags."},{"name":"Offers","description":"Folders, tags, offers, versions, workflow, templates, product lists."},{"name":"Simulator","description":"Ticket simulator (proxy to engine)."},{"name":"Coupons","description":"Templates, campaigns, coupons."},{"name":"Customers","description":"Customers, accounts, cards, balances, movements, imports."},{"name":"Counters","description":"Counter types and loyalty statuses."},{"name":"Segments","description":"Targets / segments."},{"name":"AbTests","description":"A/B tests."},{"name":"Stats","description":"ClickHouse-backed dashboards and transaction search."},{"name":"Audit","description":"Audit log."},{"name":"Webhooks","description":"Outgoing signed webhooks."},{"name":"Adapters","description":"Input format configuration (WsOne mapping)."},{"name":"Alerts","description":"Platform alerts and notification channels."},{"name":"Platform","description":"Platform settings (staff): outgoing e-mail server."},{"name":"Catalog","description":"Article catalogue of a workspace (reference, EAN, hierarchy, brand, price, image)."},{"name":"AI","description":"AI assistant (stubbed, 501)."},{"name":"Loyalty","description":"Stamp cards, rewarded actions, rewards shop."},{"name":"POS","description":"Demo POS (features.pos_demo): tickets sent to the engine with the Remizo or the Beone protocol."},{"name":"Public","description":"Unauthenticated or secret-authenticated endpoints."}],"security":[{"Bearer":[]}],"paths":{"/me":{"get":{"tags":["Me"],"operationId":"getMe","summary":"Current user, realm, staff roles, workspaces and preferences","responses":{"200":{"description":"Me","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me/workspaces":{"get":{"tags":["Me"],"operationId":"listMyWorkspaces","summary":"Workspaces the caller can open (from the organization claim ∩ workspace_members, or all for staff)","responses":{"200":{"description":"Workspaces","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceSummary"}}}}}}}}}},"/me/switch-workspace":{"post":{"tags":["Me"],"operationId":"switchWorkspace","summary":"Select the active workspace; returns its context (role, permissions, features, theme)","description":"Also stores the choice as the user's default. Front-ends call it after login and when the user switches in the workspace menu.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspace"],"properties":{"workspace":{"$ref":"#/components/schemas/Slug"}}}}}},"responses":{"200":{"description":"Context","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceContext"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/me/preferences":{"patch":{"summary":"Update my preferences","tags":["Me"],"operationId":"updateMyPreferences","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPreferences"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPreferences"}}}}}}},"/workspaces":{"get":{"tags":["Workspaces"],"operationId":"listWorkspaces","summary":"List workspaces (staff — all; studio users — their own)","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/WorkspaceStatus"}},{"name":"kind","in":"query","schema":{"$ref":"#/components/schemas/WorkspaceKind"}},{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}}]}}}}}},"post":{"tags":["Workspaces"],"operationId":"createWorkspace","summary":"Create a workspace and start provisioning (staff)","description":"Creates the row (status `provisioning`), the Keycloak Organization (alias = slug), the OpenBao path `mo/<slug>/`, then runs the deployment driver from the `WorkspaceSpec`: PG cluster, HAProxy, Valkey, engines, Silo bucket, ClickHouse partitions, RabbitMQ queue, DB migrations (`db/workspace`), seed (default counter types, loyalty statuses, templates). Progress is in `GET /workspaces/{ws}/provisioning`. Returns `202`.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceCreate"}}}},"responses":{"202":{"description":"Provisioning started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/workspaces/{ws}":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get workspace","tags":["Workspaces"],"operationId":"getWorkspace","responses":{"200":{"description":"Workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Workspaces"],"operationId":"updateWorkspace","summary":"Update name, mode, locale, timezone, spec (staff) — slug and currency are immutable","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}}}},"delete":{"tags":["Workspaces"],"operationId":"deleteWorkspace","summary":"Decommission (staff). Requires a final backup; status → deleting, then deleted after retention.","parameters":[{"name":"confirmSlug","in":"query","required":true,"schema":{"type":"string"},"description":"Must equal the slug."}],"responses":{"202":{"description":"Deletion scheduled"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/provisioning":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get provisioning status","tags":["Workspaces"],"operationId":"getProvisioningStatus","responses":{"200":{"description":"Status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisioningStatus"}}}}}}},"/workspaces/{ws}/provisioning/retry":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Workspaces"],"operationId":"retryProvisioning","summary":"Re-run failed provisioning steps (staff)","responses":{"202":{"description":"Retrying","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisioningStatus"}}}}}}},"/workspaces/{ws}/sandbox":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Workspaces"],"operationId":"createSandbox","summary":"Create (or recreate) the sandbox workspace linked to this one","description":"Sandbox = sibling workspace `<slug>-sbx`, `kind: sandbox`, light spec (1 PG, 1 engine), separate API keys (`rmz_test_…`). Offers can be copied with `POST /offers/{offerId}/duplicate` (`targetWorkspace`).","responses":{"202":{"description":"Provisioning","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}}}}},"/workspaces/{ws}/settings":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get workspace settings","tags":["Workspaces"],"operationId":"getWorkspaceSettings","responses":{"200":{"description":"Settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSettings"}}}}}},"put":{"tags":["Workspaces"],"operationId":"putWorkspaceSettings","summary":"Replace engine/business settings (pushed to the workspace DB `settings` table + control message `config.changed`)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSettings"}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSettings"}}}}}}},"/workspaces/{ws}/theme":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get workspace theme","tags":["Workspaces"],"operationId":"getWorkspaceTheme","responses":{"200":{"description":"Theme","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceTheme"}}}}}},"put":{"summary":"Put workspace theme","tags":["Workspaces"],"operationId":"putWorkspaceTheme","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceTheme"}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceTheme"}}}}}}},"/workspaces/{ws}/theme/logo":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Upload a brand image (logo, dark logo or favicon) — PNG, JPEG, WebP or SVG, at most 512 KB","description":"Stored in the workspace Silo bucket (`brand/<variant>`); the matching theme URL points to the public brand image route, versioned by content hash.","tags":["Workspaces"],"operationId":"uploadWorkspaceLogo","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"variant":{"type":"string","enum":["logo","logo_dark","favicon"],"default":"logo"}}}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceTheme"}}}}}}},"/public/brand/{ws}/{variant}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"variant","in":"path","required":true,"schema":{"type":"string","enum":["logo","logo_dark","favicon"]}}],"get":{"tags":["Public","Workspaces"],"security":[],"operationId":"getWorkspaceBrandImage","summary":"Brand image of a workspace (public, cacheable; SVG served with a sandboxing CSP)","responses":{"200":{"description":"Image","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/svg+xml":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"No such image"}}}},"/workspaces/{ws}/stores":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List stores","tags":["Workspaces"],"operationId":"listStores","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"}},{"name":"group","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Store"}}}}]}}}}}},"post":{"summary":"Create store","tags":["Workspaces"],"operationId":"createStore","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}}}}},"/workspaces/{ws}/stores/import":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Workspaces"],"operationId":"importStores","summary":"Bulk upsert stores from CSV","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportSummary"}}}}}}},"/workspaces/{ws}/stores/{storeCode}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"storeCode","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get store","tags":["Workspaces"],"operationId":"getStore","responses":{"200":{"description":"Store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}}}},"patch":{"summary":"Update store","tags":["Workspaces"],"operationId":"updateStore","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}}}},"delete":{"summary":"Delete store","tags":["Workspaces"],"operationId":"deleteStore","responses":{"204":{"description":"Deleted"}}}},"/workspaces/{ws}/store-groups":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List store groups","tags":["Workspaces"],"operationId":"listStoreGroups","responses":{"200":{"description":"Groups","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/StoreGroup"}}}}}}}}},"put":{"tags":["Workspaces"],"operationId":"putStoreGroups","summary":"Replace all store groups","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/StoreGroup"}}}}}}},"responses":{"200":{"description":"Saved"}}}},"/workspaces/{ws}/articles":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Article catalogue (search on label, reference and EAN; filters on the hierarchy and the brand)","tags":["Catalog"],"operationId":"listArticles","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"}},{"name":"department","in":"query","schema":{"type":"string"}},{"name":"family","in":"query","schema":{"type":"string"}},{"name":"subfamily","in":"query","schema":{"type":"string"}},{"name":"brand","in":"query","schema":{"type":"string"}},{"name":"active","in":"query","schema":{"type":"boolean"}},{"name":"image","in":"query","schema":{"type":"string","enum":["with","without","failed"]}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Article"}}}}]}}}}}}},"/workspaces/{ws}/articles/facets":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Values of the hierarchy and brands, with their article counts (filters, rule-based lists)","tags":["Catalog"],"operationId":"getArticleFacets","responses":{"200":{"description":"Facets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleFacets"}}}}}}},"/workspaces/{ws}/articles/images/retry":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Fetch again the images that failed","tags":["Catalog"],"operationId":"retryArticleImages","responses":{"202":{"description":"Queued","content":{"application/json":{"schema":{"type":"object","required":["queued"],"properties":{"queued":{"type":"integer"}}}}}}}}},"/workspaces/{ws}/articles/{ref}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"One article","tags":["Catalog"],"operationId":"getArticle","responses":{"200":{"description":"Article","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Article"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/workspaces/{ws}/articles/{ref}/image":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Image of an article, from the workspace Silo bucket","tags":["Catalog"],"operationId":"getArticleImage","responses":{"200":{"description":"Image","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"summary":"Replace the image of an article (JPEG, PNG, WebP, GIF or AVIF, at most 10 MB)","tags":["Catalog"],"operationId":"uploadArticleImage","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Article"}}}}}},"delete":{"summary":"Remove the image of an article","tags":["Catalog"],"operationId":"deleteArticleImage","responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Article"}}}}}}},"/overview":{"get":{"tags":["Servers"],"operationId":"getPlatformOverview","summary":"Console home — every workspace with health lights, alerts, backup age, engine throughput (staff)","responses":{"200":{"description":"Overview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformOverview"}}}}}}},"/servers":{"get":{"summary":"List servers","tags":["Servers"],"operationId":"listServers","responses":{"200":{"description":"Servers","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Server"}}}}}}}}},"post":{"tags":["Servers"],"operationId":"createServer","summary":"Register a deployment target (compose host, swarm node, k8s context)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Server"}}}}}}},"/servers/{serverId}":{"parameters":[{"name":"serverId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get server","tags":["Servers"],"operationId":"getServer","responses":{"200":{"description":"Server","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Server"}}}}}},"patch":{"summary":"Update server","tags":["Servers"],"operationId":"updateServer","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Server"}}}}}},"delete":{"summary":"Delete server","tags":["Servers"],"operationId":"deleteServer","responses":{"204":{"description":"Deleted"},"409":{"description":"Server still hosts components","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/servers/{serverId}/probe":{"parameters":[{"name":"serverId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Servers"],"operationId":"probeServer","summary":"Check connectivity, driver version, free resources","responses":{"200":{"description":"Probe result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerProbe"}}}}}}},"/workspaces/{ws}/infra":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Infra"],"operationId":"getInfraStatus","summary":"Databases (role, lag, disk), HAProxy, Valkey, engines (version, offers loaded, p99), queues","responses":{"200":{"description":"Status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InfraStatus"}}}}}}},"/workspaces/{ws}/infra/databases/{node}/restart":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/Node"}],"post":{"summary":"Restart database","tags":["Infra"],"operationId":"restartDatabase","responses":{"202":{"description":"Operation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}}}}},"/workspaces/{ws}/infra/databases/switchover":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Infra"],"operationId":"switchoverDatabase","summary":"Planned switchover to a replica (Patroni / CNPG)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"targetNode":{"type":"integer","description":"Replica index; default best candidate."}}}}}},"responses":{"202":{"description":"Operation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/infra/databases/replicas":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Add database replica","tags":["Infra"],"operationId":"addDatabaseReplica","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"serverId":{"type":"string","format":"uuid","description":"Absent: same host as the workspace (compose driver only)."}}}}}},"responses":{"202":{"description":"Operation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}}}}},"/workspaces/{ws}/infra/databases/{node}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/Node"}],"delete":{"tags":["Infra"],"operationId":"removeDatabaseReplica","summary":"Remove a replica (never the primary)","responses":{"202":{"description":"Operation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/infra/engines/{node}/restart":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/Node"}],"post":{"tags":["Infra"],"operationId":"restartEngine","summary":"Drain then restart one engine instance","responses":{"202":{"description":"Operation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}}}}},"/workspaces/{ws}/infra/engines/scale":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Scale engines","tags":["Infra"],"operationId":"scaleEngines","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["replicas"],"properties":{"replicas":{"type":"integer","minimum":1,"maximum":32}}}}}},"responses":{"202":{"description":"Operation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}}}}},"/workspaces/{ws}/infra/engines/reload":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Infra"],"operationId":"reloadEngines","summary":"Force every engine to reload offers / keys / settings","responses":{"200":{"description":"Per-engine reload result","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"instance":{"type":"string"},"ok":{"type":"boolean"},"durationMs":{"type":"number"},"error":{"type":"string"}}}}}}}}}}}},"/workspaces/{ws}/infra/placements":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get placements","tags":["Infra"],"operationId":"getPlacements","responses":{"200":{"description":"Placements","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Placement"}}}}}}}}},"put":{"tags":["Infra"],"operationId":"movePlacements","summary":"Move components to other servers (rolling)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Placement"}}}}}}},"responses":{"202":{"description":"Operation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}}}}},"/workspaces/{ws}/infra/operations":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List operations","tags":["Infra"],"operationId":"listOperations","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Operation"}}}}]}}}}}}},"/workspaces/{ws}/infra/operations/{operationId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"operationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get operation","tags":["Infra"],"operationId":"getOperation","responses":{"200":{"description":"Operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}}}}},"/backups/overview":{"get":{"tags":["Backups"],"operationId":"getBackupsOverview","summary":"All workspaces — last success age, volume, failures, untested backups (staff)","responses":{"200":{"description":"Overview","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/BackupSummary"}},"storage":{"$ref":"#/components/schemas/BackupStorage"}}}}}}}}},"/workspaces/{ws}/backups/policy":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get backup policy","tags":["Backups"],"operationId":"getBackupPolicy","responses":{"200":{"description":"Policy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicy"}}}}}},"put":{"summary":"Put backup policy","tags":["Backups"],"operationId":"putBackupPolicy","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicy"}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicy"}}}}}}},"/workspaces/{ws}/backups/summary":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get backup summary","tags":["Backups"],"operationId":"getBackupSummary","responses":{"200":{"description":"Summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupSummary"}}}}}}},"/workspaces/{ws}/backups":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List backups","tags":["Backups"],"operationId":"listBackups","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"kind","in":"query","schema":{"$ref":"#/components/schemas/BackupKind"}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/BackupStatus"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Backup"}}}}]}}}}}}},"/workspaces/{ws}/backups/run":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Run backup now","tags":["Backups"],"operationId":"runBackupNow","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/BackupKind"},"label":{"type":"string","maxLength":80}}}}}},"responses":{"202":{"description":"Backup started (cron run)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Backup"}}}}}}},"/workspaces/{ws}/backups/{backupId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"backupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get backup","tags":["Backups"],"operationId":"getBackup","responses":{"200":{"description":"Backup","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Backup"}}}}}}},"/workspaces/{ws}/backups/{backupId}/download":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"backupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Backups"],"operationId":"downloadBackup","summary":"Pre-signed Silo URL (15 min) for the backup archive (staff, audited)","responses":{"200":{"description":"Link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedLink"}}}}}}},"/workspaces/{ws}/backups/{backupId}/test-restore":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"backupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Backups"],"operationId":"testRestoreBackup","summary":"Restore into a throwaway container, run integrity checks, record the result on the backup","responses":{"202":{"description":"Restore test started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Restore"}}}}}}},"/workspaces/{ws}/backups/restore":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Backups"],"operationId":"restoreBackup","summary":"Restore to a point in time or a backup, in place or into a test workspace","description":"`target: in_place` stops the engines, restores, restarts (requires `confirmSlug`). `target: test_workspace` restores into the sandbox (or a new `<slug>-restore-<date>` workspace) without touching production.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreRequest"}}}},"responses":{"202":{"description":"Restore started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Restore"}}}},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/workspaces/{ws}/backups/restores":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List restores","tags":["Backups"],"operationId":"listRestores","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Restore"}}}}]}}}}}}},"/workspaces/{ws}/backups/restores/{restoreId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"restoreId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get restore","tags":["Backups"],"operationId":"getRestore","responses":{"200":{"description":"Restore","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Restore"}}}}}}},"/cron/jobs":{"get":{"tags":["Cron"],"operationId":"listCronJobs","summary":"All jobs, global and per workspace (staff); studio owners see their workspace via `/workspaces/{ws}/cron/jobs`","parameters":[{"name":"workspace","in":"query","schema":{"$ref":"#/components/schemas/Slug"}},{"name":"code","in":"query","schema":{"type":"string"}},{"name":"enabled","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Jobs","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CronJob"}}}}}}}}}},"/cron/preview":{"post":{"tags":["Cron"],"operationId":"previewCron","summary":"Human description and next 5 runs of a cron expression","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expression"],"properties":{"expression":{"type":"string"},"timezone":{"type":"string"}}}}}},"responses":{"200":{"description":"Preview","content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string"},"nextRuns":{"type":"array","items":{"type":"string","format":"date-time"}}}}}}},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/cron/jobs/{jobId}":{"parameters":[{"$ref":"#/components/parameters/JobId"}],"get":{"summary":"Get cron job","tags":["Cron"],"operationId":"getCronJob","responses":{"200":{"description":"Job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronJob"}}}}}},"patch":{"tags":["Cron"],"operationId":"updateCronJob","summary":"Change schedule, enabled, timeout, retries, payload","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronJobUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronJob"}}}}}}},"/cron/jobs/{jobId}/run":{"parameters":[{"$ref":"#/components/parameters/JobId"}],"post":{"summary":"Run cron job now","tags":["Cron"],"operationId":"runCronJobNow","responses":{"202":{"description":"Run queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronRun"}}}},"409":{"description":"Already running (singleton)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/cron/jobs/{jobId}/runs":{"parameters":[{"$ref":"#/components/parameters/JobId"}],"get":{"summary":"List cron runs","tags":["Cron"],"operationId":"listCronRuns","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/RunStatus"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CronRun"}}}}]}}}}}}},"/cron/runs/{runId}":{"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get cron run","tags":["Cron"],"operationId":"getCronRun","responses":{"200":{"description":"Run","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronRun"}}}}}}},"/cron/runs/{runId}/logs":{"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get cron run logs","tags":["Cron"],"operationId":"getCronRunLogs","parameters":[{"name":"tail","in":"query","schema":{"type":"integer","default":500}}],"responses":{"200":{"description":"Log lines","content":{"application/json":{"schema":{"type":"object","properties":{"lines":{"type":"array","items":{"$ref":"#/components/schemas/LogLine"}},"truncated":{"type":"boolean"},"downloadUrl":{"type":"string","format":"uri"}}}}}}}}},"/cron/runs/{runId}/cancel":{"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Cancel cron run","tags":["Cron"],"operationId":"cancelCronRun","responses":{"202":{"description":"Cancellation requested"}}}},"/workspaces/{ws}/cron/jobs":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Cron"],"operationId":"listWorkspaceCronJobs","summary":"Jobs of this workspace (owner/admin read; schedule changes allowed for business jobs only)","responses":{"200":{"description":"Jobs","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CronJob"}}}}}}}}}},"/workspaces/{ws}/sync-connectors":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List sync connectors","tags":["Sync"],"operationId":"listSyncConnectors","responses":{"200":{"description":"Connectors","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SyncConnector"}}}}}}}}},"post":{"summary":"Create sync connector","tags":["Sync"],"operationId":"createSyncConnector","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncConnectorCreate"}}}},"responses":{"201":{"description":"Created (push connectors return `secret` once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncConnector"}}}}}}},"/workspaces/{ws}/sync-mapping/preview":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Preview a mapping on a sample file (nothing is saved)","description":"Parses the sample with the given format, lists the source fields it found, and dry-runs the mapping on the first records: same parser, same CEL and same reference checks as a real run.","tags":["Sync"],"operationId":"previewSyncMapping","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mapping","sample"],"properties":{"mapping":{"$ref":"#/components/schemas/SyncMapping"},"sample":{"type":"string","maxLength":5242880,"description":"Raw sample file content (CSV, JSON, NDJSON or XML)."},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20,"description":"Records to dry-run."}}}}}},"responses":{"200":{"description":"Preview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncMappingPreview"}}}}}}},"/workspaces/{ws}/sync-connectors/{connectorId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/ConnectorId"}],"get":{"summary":"Get sync connector","tags":["Sync"],"operationId":"getSyncConnector","responses":{"200":{"description":"Connector","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncConnector"}}}}}},"patch":{"summary":"Update sync connector","tags":["Sync"],"operationId":"updateSyncConnector","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncConnectorCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncConnector"}}}}}},"delete":{"summary":"Delete sync connector","tags":["Sync"],"operationId":"deleteSyncConnector","responses":{"204":{"description":"Deleted"}}}},"/workspaces/{ws}/sync-connectors/{connectorId}/test":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/ConnectorId"}],"post":{"tags":["Sync"],"operationId":"testSyncConnector","summary":"Connectivity test (SFTP login, bucket list, API ping) and mapping dry-run on a sample","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"sample":{"type":"string","description":"Raw sample payload (CSV lines or JSON)."}}}}}},"responses":{"200":{"description":"Test result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncTestResult"}}}}}}},"/workspaces/{ws}/sync-connectors/{connectorId}/run":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/ConnectorId"}],"post":{"summary":"Run sync connector now","tags":["Sync"],"operationId":"runSyncConnectorNow","responses":{"202":{"description":"Run queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncRun"}}}}}}},"/workspaces/{ws}/sync-connectors/{connectorId}/rotate-secret":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/ConnectorId"}],"post":{"tags":["Sync"],"operationId":"rotateSyncConnectorSecret","summary":"New inbound secret (push connectors); the previous one stays valid 24h","responses":{"200":{"description":"New secret (shown once)","content":{"application/json":{"schema":{"type":"object","properties":{"secret":{"type":"string"},"previousValidUntil":{"type":"string","format":"date-time"}}}}}}}}},"/workspaces/{ws}/sync-connectors/{connectorId}/samples":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/ConnectorId"}],"get":{"tags":["Sync"],"operationId":"listSyncPushSamples","summary":"Payloads received while the push connector is listening (newest first, at most 5)","responses":{"200":{"description":"Samples","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SyncPushSample"}}}}}}}}},"delete":{"tags":["Sync"],"operationId":"clearSyncPushSamples","summary":"Forget the samples received","responses":{"204":{"description":"Cleared"}}}},"/workspaces/{ws}/sync-connectors/{connectorId}/runs":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/ConnectorId"}],"get":{"summary":"List sync runs","tags":["Sync"],"operationId":"listSyncRuns","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SyncRun"}}}}]}}}}}}},"/workspaces/{ws}/sync-runs/{runId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get sync run","tags":["Sync"],"operationId":"getSyncRun","responses":{"200":{"description":"Run","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncRun"}}}}}}},"/workspaces/{ws}/sync-runs/{runId}/cancel":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Stop a queued or running sync run","description":"The worker stops at its next checkpoint (every 500 records, between files). Records already written stay; a file stopped halfway is left in place for the next run. In a chain, the connectors after this one are not started.","tags":["Sync"],"operationId":"cancelSyncRun","responses":{"200":{"description":"Stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncRun"}}}}}}},"/workspaces/{ws}/sync-flows":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List import chains","tags":["Sync"],"operationId":"listSyncFlows","responses":{"200":{"description":"Chains","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SyncFlow"}}}}}}}}},"post":{"summary":"Create an import chain","tags":["Sync"],"operationId":"createSyncFlow","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncFlowCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncFlow"}}}}}}},"/workspaces/{ws}/sync-flows/{flowId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"flowId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get an import chain","tags":["Sync"],"operationId":"getSyncFlow","responses":{"200":{"description":"Chain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncFlow"}}}}}},"patch":{"summary":"Update an import chain","tags":["Sync"],"operationId":"updateSyncFlow","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncFlowCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncFlow"}}}}}},"delete":{"summary":"Delete an import chain","tags":["Sync"],"operationId":"deleteSyncFlow","responses":{"204":{"description":"Deleted"}}}},"/workspaces/{ws}/sync-flows/{flowId}/run":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"flowId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Run an import chain now","tags":["Sync"],"operationId":"runSyncFlow","responses":{"202":{"description":"Started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncFlowRun"}}}}}}},"/workspaces/{ws}/sync-flows/{flowId}/runs":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"flowId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Executions of an import chain, newest first","tags":["Sync"],"operationId":"listSyncFlowRuns","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Executions","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SyncFlowRun"}}}}}}}}}},"/workspaces/{ws}/sync-flow-runs/{flowRunId}/cancel":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"flowRunId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Stop an execution of an import chain","tags":["Sync"],"operationId":"cancelSyncFlowRun","responses":{"200":{"description":"Stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncFlowRun"}}}}}}},"/workspaces/{ws}/sync-runs/{runId}/rejects":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"List sync rejects","tags":["Sync"],"operationId":"listSyncRejects","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SyncReject"}}}}]}}}}}}},"/public/sync/{ws}/{connectorId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/ConnectorId"}],"post":{"tags":["Public","Sync"],"security":[{"ConnectorSecret":[]}],"operationId":"pushSyncPayload","summary":"Inbound push endpoint (push_api / push_webhook connectors)","description":"Body is the CRM payload (JSON array or object, or CSV with `text/csv`). Mapped through the connector mapping, validated, upserted into the workspace DB. Returns the run id; details via `GET /workspaces/{ws}/sync-runs/{runId}`.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{}},"text/csv":{"schema":{"type":"string"}}}},"responses":{"202":{"description":"Accepted (imported, or kept as a sample while the connector is listening)","content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":["string","null"],"format":"uuid"},"received":{"type":"integer"},"listening":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"description":"Payload too large (limit 20 MiB)"}}}},"/workspaces/{ws}/api-keys":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List api keys","tags":["ApiKeys"],"operationId":"listApiKeys","responses":{"200":{"description":"Keys (no secrets)","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}}}},"post":{"tags":["ApiKeys"],"operationId":"createApiKey","summary":"Create a key — the full key string is returned once; only its hash is stored in the workspace DB","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","required":["key"],"properties":{"key":{"type":"string","description":"rmz_<env>_<keyId>_<secret>"}}}]}}}}}}},"/workspaces/{ws}/api-keys/{keyId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get api key","tags":["ApiKeys"],"operationId":"getApiKey","responses":{"200":{"description":"Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}}}},"patch":{"tags":["ApiKeys"],"operationId":"updateApiKey","summary":"Rename, change scopes / store restriction / expiry","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}}}}},"/workspaces/{ws}/api-keys/{keyId}/revoke":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["ApiKeys"],"operationId":"revokeApiKey","summary":"Revoke immediately (engines notified by `apikeys.changed`)","responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}}}}},"/workspaces/{ws}/members":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List members","tags":["Members"],"operationId":"listMembers","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"}},{"name":"role","in":"query","schema":{"$ref":"#/components/schemas/WorkspaceRole"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}}}]}}}}}},"post":{"tags":["Members"],"operationId":"inviteMember","summary":"Invite by e-mail — creates/finds the Keycloak user in realm studio, adds it to the Organization, stores the role","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberInvite"}}}},"responses":{"201":{"description":"Invited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/members/{memberId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"memberId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"summary":"Update member","tags":["Members"],"operationId":"updateMember","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"$ref":"#/components/schemas/WorkspaceRole"},"status":{"type":"string","enum":["active","disabled"]},"storeCodes":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}}}},"delete":{"tags":["Members"],"operationId":"removeMember","summary":"Remove from the workspace (Organization membership + role). The Keycloak user is kept if member elsewhere.","responses":{"204":{"description":"Removed"},"409":{"description":"Last owner cannot be removed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/members/{memberId}/resend-invite":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"memberId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Resend invite","tags":["Members"],"operationId":"resendInvite","responses":{"202":{"description":"Sent"}}}},"/workspaces/{ws}/roles":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Members"],"operationId":"listRoles","summary":"Role catalog with permissions","responses":{"200":{"description":"Roles","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RoleDefinition"}}}}}}}}}},"/workspaces/{ws}/sso":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Get sso","tags":["Members"],"operationId":"getSso","responses":{"200":{"description":"SSO config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConfig"}}}}}},"put":{"tags":["Members"],"operationId":"putSso","summary":"Configure the Identity Provider attached to the Organization (Entra, Google, generic OIDC)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConfig"}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConfig"}}}}}},"delete":{"summary":"Delete sso","tags":["Members"],"operationId":"deleteSso","responses":{"204":{"description":"Removed"}}}},"/staff":{"get":{"tags":["Members"],"operationId":"listStaff","summary":"Editor staff (realm admin) with platform roles (staff)","responses":{"200":{"description":"Staff","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/StaffUser"}}}}}}}}},"post":{"summary":"Create staff","tags":["Members"],"operationId":"createStaff","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffUserCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffUser"}}}}}}},"/staff/{sub}":{"parameters":[{"name":"sub","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"summary":"Update staff","tags":["Members"],"operationId":"updateStaff","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffUserCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffUser"}}}}}},"delete":{"summary":"Delete staff","tags":["Members"],"operationId":"deleteStaff","responses":{"204":{"description":"Disabled"}}}},"/users/search":{"get":{"tags":["Members"],"operationId":"searchUsers","summary":"Find a studio user across workspaces (staff) — e-mail or name","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":3}}],"responses":{"200":{"description":"Users","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/UserSearchResult"}}}}}}}}}},"/platform/smtp":{"get":{"tags":["Platform"],"operationId":"getSmtpSettings","summary":"Outgoing e-mail server used for alerts and import notifications (staff)","responses":{"200":{"description":"Settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmtpSettings"}}}}}},"put":{"tags":["Platform"],"operationId":"putSmtpSettings","summary":"Change the outgoing e-mail server (staff); the password goes to OpenBao","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmtpSettingsUpdate"}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmtpSettings"}}}}}},"delete":{"tags":["Platform"],"operationId":"deleteSmtpSettings","summary":"Go back to the deployment default (REMIZO_SMTP_URL) (staff)","responses":{"200":{"description":"Reset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmtpSettings"}}}}}}},"/platform/smtp/test":{"post":{"tags":["Platform"],"operationId":"testSmtpSettings","summary":"Send a test e-mail with the saved settings, or with the settings in the body before saving them (staff)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to"],"properties":{"to":{"type":"string","format":"email"},"settings":{"$ref":"#/components/schemas/SmtpSettingsUpdate"}}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmtpTestResult"}}}}}}},"/features":{"get":{"tags":["Features"],"operationId":"listFeatureCatalog","summary":"Catalog of flags with descriptions and defaults","responses":{"200":{"description":"Catalog","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FeatureDefinition"}}}}}}}}}},"/features/defaults/{flag}":{"parameters":[{"name":"flag","in":"path","required":true,"schema":{"$ref":"#/components/schemas/FeatureFlag"}}],"put":{"tags":["Features"],"operationId":"putFeatureDefault","summary":"Change the platform default for a flag (staff)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureDefinition"}}}}}}},"/workspaces/{ws}/features":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Features"],"operationId":"getWorkspaceFeatures","summary":"Effective flags (default + overrides) with their source","responses":{"200":{"description":"Flags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceFeatures"}}}}}},"put":{"tags":["Features"],"operationId":"putWorkspaceFeatures","summary":"Set overrides (staff). `null` removes an override.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"overrides":{"type":"object","additionalProperties":{"type":["boolean","null"]}}}}}}},"responses":{"200":{"description":"Flags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceFeatures"}}}}}}},"/workspaces/{ws}/offer-folders":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Offers"],"operationId":"listOfferFolders","summary":"Folder tree","responses":{"200":{"description":"Folders","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OfferFolder"}}}}}}}}},"post":{"summary":"Create offer folder","tags":["Offers"],"operationId":"createOfferFolder","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferFolderCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferFolder"}}}}}}},"/workspaces/{ws}/offer-folders/{folderId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"folderId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"summary":"Update offer folder","tags":["Offers"],"operationId":"updateOfferFolder","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferFolderCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferFolder"}}}}}},"delete":{"tags":["Offers"],"operationId":"deleteOfferFolder","summary":"Delete an empty folder","responses":{"204":{"description":"Deleted"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/tags":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List tags","tags":["Offers"],"operationId":"listTags","responses":{"200":{"description":"Tags","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}}}}}}}}},"post":{"summary":"Create tag","tags":["Offers"],"operationId":"createTag","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}}}}},"/workspaces/{ws}/tags/{tagId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"tagId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"summary":"Update tag","tags":["Offers"],"operationId":"updateTag","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}}}},"delete":{"summary":"Delete tag","tags":["Offers"],"operationId":"deleteTag","responses":{"204":{"description":"Deleted"}}}},"/workspaces/{ws}/offers":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List offers","tags":["Offers"],"operationId":"listOffers","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"},"description":"Full-text on code, name, description."},{"name":"folderId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"tag","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":false},{"name":"status","in":"query","schema":{"type":"array","items":{"$ref":"#/components/schemas/Offer/$defs/OfferStatus"}},"style":"form","explode":false},{"name":"triggerType","in":"query","schema":{"type":"string","enum":["transaction","event","coupon"]}},{"name":"level","in":"query","schema":{"type":"integer"}},{"name":"activeAt","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only offers whose validity covers this instant."},{"name":"sort","in":"query","schema":{"type":"string","enum":["updatedAt","name","code","status","level","validFrom","validTo"],"default":"updatedAt"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OfferSummary"}}}}]}}}}}},"post":{"tags":["Offers"],"operationId":"createOffer","summary":"Create an offer with its first draft version (from scratch or from a template)","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferCreate"}}}},"responses":{"201":{"description":"Created","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDetail"}}}},"409":{"description":"Code already used","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/workspaces/{ws}/offers/{offerId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"get":{"tags":["Offers"],"operationId":"getOffer","summary":"Offer with its draft (if any) and published version","responses":{"200":{"description":"Offer","headers":{"ETag":{"schema":{"type":"string"},"description":"ETag of the draft version."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDetail"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Offers"],"operationId":"updateOfferMeta","summary":"Move to a folder, change tags (does not create a version)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"folderId":{"type":["string","null"],"format":"uuid"},"tags":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDetail"}}}}}},"delete":{"tags":["Offers"],"operationId":"archiveOffer","summary":"Archive (must not be published)","responses":{"204":{"description":"Archived"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/offers/{offerId}/draft":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"get":{"summary":"Get offer draft","tags":["Offers"],"operationId":"getOfferDraft","responses":{"200":{"description":"Draft definition","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferVersion"}}}},"404":{"description":"No draft — create one with PUT"}}},"put":{"tags":["Offers"],"operationId":"saveOfferDraft","summary":"Save the working draft (creates the draft version when none exists). Validates against offer.schema.json + referential checks; CEL checked through the engine.","description":"Optimistic locking with `If-Match`. A draft that was `pending_approval` goes back to `draft` when edited.","parameters":[{"name":"If-Match","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"responses":{"200":{"description":"Saved","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferVersion"}}}},"412":{"description":"ETag mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"summary":"Discard offer draft","tags":["Offers"],"operationId":"discardOfferDraft","responses":{"204":{"description":"Discarded"}}}},"/workspaces/{ws}/offers/{offerId}/validate":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"validateOffer","summary":"Validate a definition without saving (schema, references, CEL, limits coherence, conflicts with published offers)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"responses":{"200":{"description":"Validation report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationReport"}}}}}}},"/workspaces/{ws}/offers/{offerId}/versions":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"get":{"summary":"List offer versions","tags":["Offers"],"operationId":"listOfferVersions","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OfferVersionSummary"}}}}]}}}}}}},"/workspaces/{ws}/offers/{offerId}/versions/{versionNo}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"},{"name":"versionNo","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get offer version","tags":["Offers"],"operationId":"getOfferVersion","responses":{"200":{"description":"Version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferVersion"}}}}}}},"/workspaces/{ws}/offers/{offerId}/diff":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"get":{"tags":["Offers"],"operationId":"diffOfferVersions","summary":"Structured diff between two versions (default: draft vs published)","parameters":[{"name":"from","in":"query","schema":{"type":"integer"}},{"name":"to","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Diff","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDiff"}}}}}}},"/workspaces/{ws}/offers/{offerId}/submit":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"submitOffer","summary":"Draft → pending_approval","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"type":"string","maxLength":1000}}}}}},"responses":{"200":{"description":"Submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDetail"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/offers/{offerId}/approve":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"approveOffer","summary":"pending_approval → approved (role approver/admin/owner; not the submitter unless `workflow.allowSelfApproval`)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"type":"string","maxLength":1000}}}}}},"responses":{"200":{"description":"Approved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDetail"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/offers/{offerId}/reject":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"rejectOffer","summary":"pending_approval → draft with a mandatory comment","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"type":"string","minLength":1,"maxLength":1000}}}}}},"responses":{"200":{"description":"Rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDetail"}}}}}}},"/workspaces/{ws}/offers/{offerId}/publish":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"publishOffer","summary":"approved (or draft when `workflow.requireApproval` is false) → published; previous published version → superseded","description":"Transaction in the workspace DB (version status, offers.published_version_id) + RabbitMQ `offers.published` + synchronous `POST /mgmt/v1/reload` on every engine (best effort, result reported). Idempotent.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"versionNo":{"type":"integer","description":"Default = the approved/draft version."},"comment":{"type":"string"}}}}}},"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResult"}}}},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/workspaces/{ws}/offers/{offerId}/rollback":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"rollbackOffer","summary":"One-click rollback — republish a previous version (creates a new version number copying it)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["versionNo"],"properties":{"versionNo":{"type":"integer"},"comment":{"type":"string"}}}}}},"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResult"}}}}}}},"/workspaces/{ws}/offers/{offerId}/suspend":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"suspendOffer","summary":"Emergency stop — published → suspended, engines unload it within seconds","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"maxLength":500}}}}}},"responses":{"200":{"description":"Suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResult"}}}}}}},"/workspaces/{ws}/offers/{offerId}/resume":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"resumeOffer","summary":"suspended → published","responses":{"200":{"description":"Resumed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResult"}}}}}}},"/workspaces/{ws}/offers/{offerId}/unpublish":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"unpublishOffer","summary":"published/suspended → approved (removed from engines, kept as approved version)","responses":{"200":{"description":"Unpublished","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResult"}}}}}}},"/workspaces/{ws}/offers/{offerId}/duplicate":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"}],"post":{"tags":["Offers"],"operationId":"duplicateOffer","summary":"Copy as a new draft offer, optionally into another workspace the caller can access (sandbox ↔ production)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"targetWorkspace":{"$ref":"#/components/schemas/Slug"},"folderId":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDetail"}}}}}}},"/workspaces/{ws}/offers/{offerId}/stats":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/OfferId"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"}],"get":{"tags":["Offers","Stats"],"operationId":"getOfferQuickStats","summary":"Uses, discount, points, coupons, budget consumption for the offer card","responses":{"200":{"description":"Stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferStats"}}}}}}},"/workspaces/{ws}/offer-templates":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Offers"],"operationId":"listOfferTemplates","summary":"Ready-made templates (platform + workspace)","parameters":[{"name":"category","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Templates","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OfferTemplate"}}}}}}}}},"post":{"tags":["Offers"],"operationId":"createOfferTemplate","summary":"Save an offer definition as a workspace template","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferTemplate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferTemplate"}}}}}}},"/workspaces/{ws}/offer-templates/{templateId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"templateId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get offer template","tags":["Offers"],"operationId":"getOfferTemplate","responses":{"200":{"description":"Template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferTemplate"}}}}}},"delete":{"summary":"Delete offer template","tags":["Offers"],"operationId":"deleteOfferTemplate","responses":{"204":{"description":"Deleted"}}}},"/workspaces/{ws}/product-lists":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List product lists","tags":["Offers"],"operationId":"listProductLists","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductList"}}}}]}}}}}},"post":{"summary":"Create product list","tags":["Offers"],"operationId":"createProductList","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductListCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}}}}},"/workspaces/{ws}/product-lists/preview":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Preview the articles of the catalogue that match rules","tags":["Offers"],"operationId":"previewProductListRules","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rules"],"properties":{"rules":{"$ref":"#/components/schemas/ProductListRules"}}}}}},"responses":{"200":{"description":"Preview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductListRulesPreview"}}}},"422":{"description":"Invalid rules","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/product-lists/{listId}/refresh":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"listId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Recompute a rules list from the catalogue now","tags":["Offers"],"operationId":"refreshProductList","responses":{"200":{"description":"Recomputed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"409":{"description":"Not a rules list","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/product-lists/{listId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"listId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get product list","tags":["Offers"],"operationId":"getProductList","responses":{"200":{"description":"List","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}}}},"patch":{"summary":"Update product list","tags":["Offers"],"operationId":"updateProductList","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductListCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}}}},"delete":{"summary":"Delete product list","tags":["Offers"],"operationId":"deleteProductList","responses":{"204":{"description":"Deleted"},"409":{"description":"Referenced by offers","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/product-lists/{listId}/items":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"listId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"List product list items","tags":["Offers"],"operationId":"listProductListItems","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductListItem"}}}}]}}}}}},"put":{"tags":["Offers"],"operationId":"replaceProductListItems","summary":"Replace all items (JSON ≤ 50 000 items) — engines reload `product_lists`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductListItem"}}}}}}},"responses":{"200":{"description":"Replaced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportSummary"}}}}}}},"/workspaces/{ws}/product-lists/{listId}/items/import":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"listId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Offers"],"operationId":"importProductListItems","summary":"CSV import (columns ean, ref, label), mode replace|append","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"mode":{"type":"string","enum":["replace","append"],"default":"replace"}}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportSummary"}}}}}}},"/workspaces/{ws}/simulator/evaluate":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Simulator"],"operationId":"simulate","summary":"Evaluate a ticket against drafts and/or published offers (proxy to engine `POST /mgmt/v1/simulate`)","description":"`drafts` are offer ids whose current draft version is sent to the engine; `offerIds` restricts the published set. Returns the full result with `criteriaTrace`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateRequest"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationResult"}}}},"422":{"$ref":"#/components/responses/Unprocessable"},"503":{"description":"No engine reachable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/simulator/tickets":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Simulator"],"operationId":"listSampleTickets","summary":"Saved sample tickets (shared in the workspace)","responses":{"200":{"description":"Samples","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SampleTicket"}}}}}}}}},"post":{"summary":"Create sample ticket","tags":["Simulator"],"operationId":"createSampleTicket","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleTicket"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleTicket"}}}}}}},"/workspaces/{ws}/simulator/tickets/{sampleId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"sampleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"summary":"Update sample ticket","tags":["Simulator"],"operationId":"updateSampleTicket","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleTicket"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleTicket"}}}}}},"delete":{"summary":"Delete sample ticket","tags":["Simulator"],"operationId":"deleteSampleTicket","responses":{"204":{"description":"Deleted"}}}},"/workspaces/{ws}/coupon-templates":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List coupon templates","tags":["Coupons"],"operationId":"listCouponTemplates","responses":{"200":{"description":"Templates","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CouponTemplate"}}}}}}}}},"post":{"summary":"Create coupon template","tags":["Coupons"],"operationId":"createCouponTemplate","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponTemplateCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponTemplate"}}}}}}},"/workspaces/{ws}/coupon-templates/{templateId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/TemplateId"}],"get":{"summary":"Get coupon template","tags":["Coupons"],"operationId":"getCouponTemplate","responses":{"200":{"description":"Template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponTemplate"}}}}}},"patch":{"summary":"Update coupon template","tags":["Coupons"],"operationId":"updateCouponTemplate","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponTemplateCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponTemplate"}}}}}},"delete":{"summary":"Delete coupon template","tags":["Coupons"],"operationId":"deleteCouponTemplate","responses":{"204":{"description":"Deleted"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/coupon-templates/{templateId}/preview":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/TemplateId"}],"post":{"tags":["Coupons"],"operationId":"previewCouponTemplate","summary":"Render the design with sample data (synchronous, PNG or PDF)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string","enum":["png","pdf"],"default":"png"},"design":{"$ref":"#/components/schemas/CouponDesign"},"sample":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Rendered file","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}}}}},"/workspaces/{ws}/coupon-campaigns":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List coupon campaigns","tags":["Coupons"],"operationId":"listCouponCampaigns","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/CampaignStatus"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CouponCampaign"}}}}]}}}}}},"post":{"summary":"Create coupon campaign","tags":["Coupons"],"operationId":"createCouponCampaign","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponCampaignCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponCampaign"}}}}}}},"/workspaces/{ws}/coupon-campaigns/{campaignId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CampaignId"}],"get":{"summary":"Get coupon campaign","tags":["Coupons"],"operationId":"getCouponCampaign","responses":{"200":{"description":"Campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponCampaign"}}}}}},"patch":{"summary":"Update coupon campaign","tags":["Coupons"],"operationId":"updateCouponCampaign","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponCampaignCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponCampaign"}}}}}}},"/workspaces/{ws}/coupon-campaigns/{campaignId}/generate":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CampaignId"}],"post":{"tags":["Coupons"],"operationId":"generateCampaignCoupons","summary":"Bulk generation (background job); optional assignment to a segment or customer list","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quantity"],"properties":{"quantity":{"type":"integer","minimum":1,"maximum":5000000},"assignTo":{"type":"object","properties":{"segmentId":{"type":"string","format":"uuid"},"customerIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},"render":{"type":"boolean","default":false,"description":"Also render PNG/PDF for each coupon."}}}}}},"responses":{"202":{"description":"Job queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponCampaign"}}}}}}},"/workspaces/{ws}/coupon-campaigns/{campaignId}/import":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CampaignId"}],"post":{"tags":["Coupons"],"operationId":"importCampaignCoupons","summary":"Import external codes (CSV columns code, customerExternalRef?, validFrom?, validTo?)","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"202":{"description":"Job queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportSummary"}}}}}}},"/workspaces/{ws}/coupon-campaigns/{campaignId}/export":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CampaignId"}],"get":{"tags":["Coupons"],"operationId":"exportCampaignCoupons","summary":"Pre-signed link to a CSV of the campaign codes (state, customer, dates)","responses":{"200":{"description":"Link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedLink"}}}}}}},"/workspaces/{ws}/coupon-campaigns/{campaignId}/cancel":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CampaignId"}],"post":{"tags":["Coupons"],"operationId":"cancelCampaign","summary":"Cancel every coupon still available","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}}}}},"responses":{"202":{"description":"Cancelling","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponCampaign"}}}}}}},"/workspaces/{ws}/coupons":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List coupons","tags":["Coupons"],"operationId":"listCoupons","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"code","in":"query","schema":{"type":"string"}},{"name":"state","in":"query","schema":{"type":"array","items":{"$ref":"#/components/schemas/CouponState"}},"style":"form","explode":false},{"name":"templateId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"campaignId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"offerId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"customerId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"issuedFrom","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"issuedTo","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Coupon"}}}}]}}}}}}},"/workspaces/{ws}/coupons/{couponId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CouponId"}],"get":{"summary":"Get coupon","tags":["Coupons"],"operationId":"getCoupon","responses":{"200":{"description":"Coupon with events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Coupon"}}}}}}},"/workspaces/{ws}/coupons/{couponId}/cancel":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CouponId"}],"post":{"summary":"Cancel coupon","tags":["Coupons"],"operationId":"cancelCoupon","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Coupon"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/coupons/{couponId}/extend":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CouponId"}],"post":{"tags":["Coupons"],"operationId":"extendCoupon","summary":"Push validTo later (available/expired coupons; expired → available)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["validTo","reason"],"properties":{"validTo":{"type":"string","format":"date-time"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Extended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Coupon"}}}}}}},"/workspaces/{ws}/coupons/{couponId}/rerender":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CouponId"}],"post":{"summary":"Rerender coupon","tags":["Coupons"],"operationId":"rerenderCoupon","responses":{"202":{"description":"Render job queued"}}}},"/public/coupons/{token}":{"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"},"description":"Signed token (HMAC, embeds couponId + exp)."}],"get":{"tags":["Public","Coupons"],"security":[],"operationId":"getRenderedCoupon","summary":"Download the rendered coupon (PNG/PDF). 202 while rendering, 410 when expired.","responses":{"200":{"description":"File","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"202":{"description":"Still rendering","headers":{"Retry-After":{"schema":{"type":"integer"}}}},"404":{"description":"Unknown token"},"410":{"description":"Link expired or coupon cancelled"}}}},"/workspaces/{ws}/customers":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Search customers","tags":["Customers"],"operationId":"searchCustomers","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"},"description":"Card number, e-mail, phone, name, external ref."},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/CustomerStatus"}},{"name":"loyaltyStatus","in":"query","schema":{"type":"string"}},{"name":"storeCode","in":"query","schema":{"type":"string"}},{"name":"segmentId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"tag","in":"query","schema":{"type":"string"}},{"name":"createdFrom","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"createdTo","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CustomerSummary"}}}}]}}}}}},"post":{"tags":["Customers"],"operationId":"createCustomer","summary":"Create a customer (autonomous mode, or synced mode with `allowManualCreate`)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/customers/{customerId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"tags":["Customers"],"operationId":"getCustomer","summary":"Profile, cards, identifiers, balances, tags, segments","responses":{"200":{"description":"Customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Customers"],"operationId":"updateCustomer","summary":"Update profile fields (in synced mode only fields listed in `customers.editableFields`)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}}}}},"/workspaces/{ws}/customers/{customerId}/balances":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"summary":"Get customer balances","tags":["Customers"],"operationId":"getCustomerBalances","responses":{"200":{"description":"Balances","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/Balance"}}}}}}}}}},"/workspaces/{ws}/customers/{customerId}/movements":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"tags":["Customers"],"operationId":"listCustomerMovements","summary":"Ledger movements (append-only), newest first","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"counterTypeCode","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"$ref":"#/components/schemas/MovementKind"}},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Movement"}}}}]}}}}}}},"/workspaces/{ws}/customers/{customerId}/adjustments":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"post":{"tags":["Customers"],"operationId":"createAdjustment","summary":"Manual credit/debit (geste commercial) — reason mandatory, audited, movement kind `adjust`","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdjustmentCreate"}}}},"responses":{"201":{"description":"Movement","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Movement"}}}},"409":{"description":"Would go negative and counter forbids it","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/workspaces/{ws}/customers/{customerId}/coupons":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"summary":"List customer coupons","tags":["Customers"],"operationId":"listCustomerCoupons","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"state","in":"query","schema":{"type":"array","items":{"$ref":"#/components/schemas/CouponState"}},"style":"form","explode":false}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Coupon"}}}}]}}}}}}},"/workspaces/{ws}/customers/{customerId}/transactions":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"summary":"List customer transactions","tags":["Customers"],"operationId":"listCustomerTransactions","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TransactionSummary"}}}}]}}}}}}},"/workspaces/{ws}/customers/{customerId}/cards":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"post":{"summary":"Add card","tags":["Customers"],"operationId":"addCard","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardCreate"}}}},"responses":{"201":{"description":"Card","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Card"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/customers/{customerId}/cards/{cardId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"},{"name":"cardId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"tags":["Customers"],"operationId":"updateCard","summary":"Block / unblock / replace (status change)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/CardStatus"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Card","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Card"}}}}}}},"/workspaces/{ws}/customers/{customerId}/anonymize":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"post":{"tags":["Customers"],"operationId":"anonymizeCustomer","summary":"GDPR right to be forgotten — irreversible pseudonymisation of personal fields, cards blocked, coupons cancelled, ledger kept","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason","confirm"],"properties":{"reason":{"type":"string"},"confirm":{"type":"boolean","const":true}}}}}},"responses":{"200":{"description":"Anonymized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}}}}},"/workspaces/{ws}/customers/merge":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Customers"],"operationId":"mergeCustomers","summary":"Merge source into target — cards, identifiers, coupons, balances (transfer movements) move to target; source → merged","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sourceCustomerId","targetCustomerId","reason"],"properties":{"sourceCustomerId":{"type":"string","format":"uuid"},"targetCustomerId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"profileFrom":{"type":"string","enum":["target","source"],"default":"target"}}}}}},"responses":{"200":{"description":"Merged target","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/customers/imports":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List customer imports","tags":["Customers"],"operationId":"listCustomerImports","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ImportJob"}}}}]}}}}}},"post":{"tags":["Customers"],"operationId":"importCustomers","summary":"Bulk import (CSV/JSON, ≤ 200 MiB) with a mapping; dry-run option","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"mapping":{"type":"string","description":"JSON of SyncMapping."},"dryRun":{"type":"boolean","default":false},"mode":{"type":"string","enum":["upsert","insert_only","update_only"],"default":"upsert"}}}}}},"responses":{"202":{"description":"Job queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}}}}},"/workspaces/{ws}/customers/imports/{importId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"importId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get customer import","tags":["Customers"],"operationId":"getCustomerImport","responses":{"200":{"description":"Job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}}}}},"/workspaces/{ws}/customers/imports/{importId}/rejects":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"importId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"List customer import rejects","tags":["Customers"],"operationId":"listCustomerImportRejects","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SyncReject"}}}}]}}}}}}},"/workspaces/{ws}/customers/{customerId}/events":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"summary":"List customer events","tags":["Customers"],"operationId":"listCustomerEvents","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CustomerEvent"}}}}]}}}}}},"post":{"tags":["Customers"],"operationId":"emitCustomerEvent","summary":"Emit a customer event (forwarded to the engine `POST /v1/events`)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerEventCreate"}}}},"responses":{"200":{"description":"Evaluation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationResult"}}}}}}},"/workspaces/{ws}/counter-types":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List counter types","tags":["Counters"],"operationId":"listCounterTypes","responses":{"200":{"description":"Counter types","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CounterType"}}}}}}}}},"post":{"summary":"Create counter type","tags":["Counters"],"operationId":"createCounterType","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterType"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterType"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/counter-types/{code}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"code","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get counter type","tags":["Counters"],"operationId":"getCounterType","responses":{"200":{"description":"Counter type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterType"}}}}}},"patch":{"tags":["Counters"],"operationId":"updateCounterType","summary":"Update label, expiry, periodic reset, caps (kind and code immutable)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterType"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterType"}}}}}},"delete":{"tags":["Counters"],"operationId":"archiveCounterType","summary":"Archive (only when no movement exists)","responses":{"204":{"description":"Archived"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/loyalty-statuses":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List loyalty statuses","tags":["Counters"],"operationId":"listLoyaltyStatuses","responses":{"200":{"description":"Statuses","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/LoyaltyStatus"}}}}}}}}},"put":{"tags":["Counters"],"operationId":"putLoyaltyStatuses","summary":"Replace the ordered list of statuses (tiers)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/LoyaltyStatus"}}}}}}},"responses":{"200":{"description":"Saved"}}}},"/workspaces/{ws}/segments":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List segments","tags":["Segments"],"operationId":"listSegments","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Segment"}}}}]}}}}}},"post":{"summary":"Create segment","tags":["Segments"],"operationId":"createSegment","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Segment"}}}}}}},"/workspaces/{ws}/segments/estimate":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Segments"],"operationId":"estimateSegment","summary":"Estimated size of a definition (sampled count, < 2 s)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentDefinition"}}}},"responses":{"200":{"description":"Estimate","content":{"application/json":{"schema":{"type":"object","properties":{"estimatedSize":{"type":"integer"},"population":{"type":"integer"},"sampled":{"type":"boolean"}}}}}}}}},"/workspaces/{ws}/segments/{segmentId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/SegmentId"}],"get":{"summary":"Get segment","tags":["Segments"],"operationId":"getSegment","responses":{"200":{"description":"Segment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Segment"}}}}}},"patch":{"summary":"Update segment","tags":["Segments"],"operationId":"updateSegment","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Segment"}}}}}},"delete":{"summary":"Delete segment","tags":["Segments"],"operationId":"deleteSegment","responses":{"204":{"description":"Deleted"},"409":{"description":"Referenced by offers","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/segments/{segmentId}/compute":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/SegmentId"}],"post":{"summary":"Compute segment now","tags":["Segments"],"operationId":"computeSegmentNow","responses":{"202":{"description":"Run queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentRun"}}}}}}},"/workspaces/{ws}/segments/{segmentId}/schedule":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/SegmentId"}],"put":{"tags":["Segments"],"operationId":"scheduleSegment","summary":"Cron schedule for recomputation (creates/updates the `segments.compute` job for this segment)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cron":{"type":["string","null"],"description":"null disables."},"timezone":{"type":"string"}}}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Segment"}}}}}}},"/workspaces/{ws}/segments/{segmentId}/members":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/SegmentId"}],"get":{"summary":"List segment members","tags":["Segments"],"operationId":"listSegmentMembers","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CustomerSummary"}}}}]}}}}}}},"/workspaces/{ws}/segments/{segmentId}/runs":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/SegmentId"}],"get":{"summary":"List segment runs","tags":["Segments"],"operationId":"listSegmentRuns","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SegmentRun"}}}}]}}}}}}},"/workspaces/{ws}/ab-tests":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List ab tests","tags":["AbTests"],"operationId":"listAbTests","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/AbTestStatus"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AbTest"}}}}]}}}}}},"post":{"summary":"Create ab test","tags":["AbTests"],"operationId":"createAbTest","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTestCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTest"}}}}}}},"/workspaces/{ws}/ab-tests/{testId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/TestId"}],"get":{"summary":"Get ab test","tags":["AbTests"],"operationId":"getAbTest","responses":{"200":{"description":"Test","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTest"}}}}}},"patch":{"tags":["AbTests"],"operationId":"updateAbTest","summary":"Editable while draft; only name/description/endAt while running","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTestCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTest"}}}}}}},"/workspaces/{ws}/ab-tests/{testId}/start":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/TestId"}],"post":{"summary":"Start ab test","tags":["AbTests"],"operationId":"startAbTest","responses":{"200":{"description":"Running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTest"}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/workspaces/{ws}/ab-tests/{testId}/stop":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/TestId"}],"post":{"summary":"Stop ab test","tags":["AbTests"],"operationId":"stopAbTest","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"winnerVariant":{"type":"string"},"comment":{"type":"string"}}}}}},"responses":{"200":{"description":"Stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTest"}}}}}}},"/workspaces/{ws}/ab-tests/{testId}/results":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/TestId"}],"get":{"tags":["AbTests","Stats"],"operationId":"getAbTestResults","summary":"Per-variant exposure, conversion, basket uplift, discount cost, ROI (ClickHouse)","responses":{"200":{"description":"Results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbTestResults"}}}}}}},"/workspaces/{ws}/stats/overview":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"$ref":"#/components/parameters/StoreFilter"}],"get":{"tags":["Stats"],"operationId":"getStatsOverview","summary":"KPI tiles — tickets, identified rate, discount, points earned/burnt, coupons issued/redeemed, with previous-period deltas","responses":{"200":{"description":"KPIs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsOverview"}}}}}}},"/workspaces/{ws}/stats/timeseries":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"$ref":"#/components/parameters/StoreFilter"}],"get":{"summary":"Get stats timeseries","tags":["Stats"],"operationId":"getStatsTimeseries","parameters":[{"name":"metric","in":"query","required":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/StatsMetric"}},"style":"form","explode":false},{"name":"granularity","in":"query","schema":{"type":"string","enum":["hour","day","week","month"],"default":"day"}},{"name":"offerId","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Series","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Timeseries"}}}}}}},"/workspaces/{ws}/stats/offers":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"$ref":"#/components/parameters/StoreFilter"}],"get":{"summary":"Get stats by offer","tags":["Stats"],"operationId":"getStatsByOffer","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"sort","in":"query","schema":{"type":"string","enum":["uses","discountCents","points","coupons","rejected"],"default":"uses"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OfferStats"}}}}]}}}}}}},"/workspaces/{ws}/stats/stores":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"}],"get":{"summary":"Get stats by store","tags":["Stats"],"operationId":"getStatsByStore","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"offerId","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/StoreStats"}}}}]}}}}}}},"/workspaces/{ws}/stats/points":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"$ref":"#/components/parameters/StoreFilter"}],"get":{"tags":["Stats"],"operationId":"getStatsPoints","summary":"Per counter type — earned, burnt, expired, adjusted, outstanding liability","responses":{"200":{"description":"Points stats","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CounterStats"}}}}}}}}}},"/workspaces/{ws}/stats/coupons":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"$ref":"#/components/parameters/StoreFilter"}],"get":{"tags":["Stats"],"operationId":"getStatsCoupons","summary":"Per template/campaign — issued, redeemed, expired, redemption rate, median delay","responses":{"200":{"description":"Coupon stats","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CouponStats"}}}}}}}}}},"/workspaces/{ws}/stats/customers":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"}],"get":{"tags":["Stats"],"operationId":"getStatsCustomers","summary":"Active customers, new cards, identified basket vs anonymous, by loyalty status","responses":{"200":{"description":"Customer stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerStats"}}}}}}},"/workspaces/{ws}/stats/export":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["Stats"],"operationId":"exportStats","summary":"Async CSV/XLSX export of any stats query → pre-signed link","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["report"],"properties":{"report":{"type":"string","enum":["offers","stores","points","coupons","transactions","movements"]},"format":{"type":"string","enum":["csv","xlsx"],"default":"csv"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"filters":{"type":"object","additionalProperties":true}}}}}},"responses":{"202":{"description":"Export job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJob"}}}}}}},"/workspaces/{ws}/exports/{exportId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"exportId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get export","tags":["Stats"],"operationId":"getExport","responses":{"200":{"description":"Export job (with link when ready)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJob"}}}}}}},"/workspaces/{ws}/transactions":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"tags":["Stats"],"operationId":"searchTransactions","summary":"Ticket search (ClickHouse for history, workspace PG for the last 48 h)","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"name":"ticketKey","in":"query","schema":{"type":"string"}},{"name":"storeCode","in":"query","schema":{"type":"string"}},{"name":"posId","in":"query","schema":{"type":"string"}},{"name":"customerId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"offerId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"state","in":"query","schema":{"$ref":"#/components/schemas/TransactionState"}},{"name":"minDiscountCents","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TransactionSummary"}}}}]}}}}}}},"/workspaces/{ws}/transactions/{transactionId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"transactionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Stats"],"operationId":"getTransactionDetail","summary":"Full ticket and evaluation result (as stored by the engine)","responses":{"200":{"description":"Transaction","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Engine_Transaction"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/workspaces/{ws}/audit":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List workspace audit","tags":["Audit"],"operationId":"listWorkspaceAudit","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"name":"actor","in":"query","schema":{"type":"string"}},{"name":"action","in":"query","schema":{"type":"string"}},{"name":"targetType","in":"query","schema":{"type":"string"}},{"name":"targetId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AuditEntry"}}}}]}}}}}}},"/audit":{"get":{"tags":["Audit"],"operationId":"listPlatformAudit","summary":"Platform-level audit (staff) — workspaces, servers, backups, staff, feature flags","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"name":"workspace","in":"query","schema":{"$ref":"#/components/schemas/Slug"}},{"name":"actor","in":"query","schema":{"type":"string"}},{"name":"action","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AuditEntry"}}}}]}}}}}}},"/webhook-events":{"get":{"tags":["Webhooks"],"operationId":"listWebhookEventTypes","summary":"Catalog of outgoing event types with payload schemas","security":[],"responses":{"200":{"description":"Catalog","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/WebhookEventType"},"description":{"type":"string"},"payloadSchema":{"type":"object"}}}}}}}}}}}},"/workspaces/{ws}/webhooks":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List webhooks","tags":["Webhooks"],"operationId":"listWebhooks","responses":{"200":{"description":"Webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}}}},"post":{"summary":"Create webhook","tags":["Webhooks"],"operationId":"createWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}}},"responses":{"201":{"description":"Created (secret shown once)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string"}}}]}}}}}}},"/workspaces/{ws}/webhooks/{webhookId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/WebhookId"}],"get":{"summary":"Get webhook","tags":["Webhooks"],"operationId":"getWebhook","responses":{"200":{"description":"Webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}}}},"patch":{"summary":"Update webhook","tags":["Webhooks"],"operationId":"updateWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}}}},"delete":{"summary":"Delete webhook","tags":["Webhooks"],"operationId":"deleteWebhook","responses":{"204":{"description":"Deleted"}}}},"/workspaces/{ws}/webhooks/{webhookId}/test":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/WebhookId"}],"post":{"tags":["Webhooks"],"operationId":"testWebhook","summary":"Send a `ping` event synchronously","responses":{"200":{"description":"Delivery result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}}},"/workspaces/{ws}/webhooks/{webhookId}/rotate-secret":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/WebhookId"}],"post":{"summary":"Rotate webhook secret","tags":["Webhooks"],"operationId":"rotateWebhookSecret","responses":{"200":{"description":"New secret (shown once)","content":{"application/json":{"schema":{"type":"object","properties":{"secret":{"type":"string"},"previousValidUntil":{"type":"string","format":"date-time"}}}}}}}}},"/workspaces/{ws}/webhooks/{webhookId}/deliveries":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/WebhookId"}],"get":{"summary":"List webhook deliveries","tags":["Webhooks"],"operationId":"listWebhookDeliveries","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/DeliveryStatus"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}]}}}}}}},"/workspaces/{ws}/webhooks/deliveries/{deliveryId}/retry":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Retry webhook delivery","tags":["Webhooks"],"operationId":"retryWebhookDelivery","responses":{"202":{"description":"Queued"}}}},"/workspaces/{ws}/adapters":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List adapters","tags":["Adapters"],"operationId":"listAdapters","responses":{"200":{"description":"Adapters","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AdapterConfig"}}}}}}}}}},"/workspaces/{ws}/adapters/{kind}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"kind","in":"path","required":true,"schema":{"$ref":"#/components/schemas/AdapterKind"}}],"get":{"summary":"Get adapter","tags":["Adapters"],"operationId":"getAdapter","responses":{"200":{"description":"Adapter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdapterConfig"}}}}}},"put":{"tags":["Adapters"],"operationId":"putAdapter","summary":"Enable/disable and configure the mapping (pushed to engines via `config.changed`)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdapterConfig"}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdapterConfig"}}}}}}},"/workspaces/{ws}/adapters/{kind}/test":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"kind","in":"path","required":true,"schema":{"$ref":"#/components/schemas/AdapterKind"}}],"post":{"tags":["Adapters"],"operationId":"testAdapterMapping","summary":"Map a raw vendor payload to the canonical ticket (no evaluation)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["payload"],"properties":{"payload":{},"config":{"$ref":"#/components/schemas/AdapterConfig"}}}}}},"responses":{"200":{"description":"Mapped ticket","content":{"application/json":{"schema":{"type":"object","properties":{"ticket":{"$ref":"#/components/schemas/Ticket"},"warnings":{"type":"array","items":{"type":"string"}}}}}}},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/alerts":{"get":{"tags":["Alerts"],"operationId":"listAlerts","summary":"Active and recent platform alerts (staff); studio owners get their workspace's via `?workspace=`","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"name":"workspace","in":"query","schema":{"$ref":"#/components/schemas/Slug"}},{"name":"state","in":"query","schema":{"type":"string","enum":["open","acknowledged","resolved"]}},{"name":"severity","in":"query","schema":{"$ref":"#/components/schemas/Severity"}}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Alert"}}}}]}}}}}}},"/alerts/{alertId}/ack":{"parameters":[{"name":"alertId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Acknowledge alert","tags":["Alerts"],"operationId":"acknowledgeAlert","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"type":"string"}}}}}},"responses":{"200":{"description":"Acknowledged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Alert"}}}}}}},"/alerts/channels":{"get":{"summary":"List alert channels","tags":["Alerts"],"operationId":"listAlertChannels","responses":{"200":{"description":"Channels","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AlertChannel"}}}}}}}}},"put":{"tags":["Alerts"],"operationId":"putAlertChannels","summary":"Replace notification channels (Teams webhooks, e-mail), global or per workspace","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AlertChannel"}}}}}}},"responses":{"200":{"description":"Saved"}}}},"/workspaces/{ws}/ai/offer-from-text":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["AI"],"operationId":"aiOfferFromText","summary":"Draft an offer definition from a natural-language description (NOT IMPLEMENTED — 501; 404 when features.ai is off)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":4000},"locale":{"type":"string"}}}}}},"responses":{"200":{"description":"Suggested definition","content":{"application/json":{"schema":{"type":"object","properties":{"offer":{"$ref":"#/components/schemas/Offer"},"explanation":{"type":"string"},"confidence":{"type":"number"}}}}}},"404":{"description":"features.ai disabled"},"501":{"$ref":"#/components/responses/NotImplemented"}}}},"/workspaces/{ws}/ai/conflicts":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["AI"],"operationId":"aiDetectConflicts","summary":"Detect conflicts between a draft and published offers (NOT IMPLEMENTED — 501)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"offerId":{"type":"string","format":"uuid"},"offer":{"$ref":"#/components/schemas/Offer"}}}}}},"responses":{"200":{"description":"Conflicts","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"withOfferId":{"type":"string","format":"uuid"},"kind":{"type":"string"},"severity":{"$ref":"#/components/schemas/Severity"},"explanation":{"type":"string"}}}}}}}}},"404":{"description":"features.ai disabled"},"501":{"$ref":"#/components/responses/NotImplemented"}}}},"/workspaces/{ws}/ai/impact":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"tags":["AI"],"operationId":"aiEstimateImpact","summary":"Estimate cost and reach of an offer on ticket history (NOT IMPLEMENTED — 501)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["offer"],"properties":{"offer":{"$ref":"#/components/schemas/Offer"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Impact","content":{"application/json":{"schema":{"type":"object","properties":{"ticketsAffected":{"type":"integer"},"discountCents":{"type":"integer"},"points":{"type":"integer"},"confidence":{"type":"number"}}}}}},"404":{"description":"features.ai disabled"},"501":{"$ref":"#/components/responses/NotImplemented"}}}},"/public/health":{"get":{"summary":"Hub health","tags":["Public"],"security":[],"operationId":"hubHealth","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"version":{"type":"string"},"checks":{"type":"object","additionalProperties":{"type":"string"}}}}}}}}}},"/workspaces/{ws}/stamp-cards":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List stamp cards","tags":["Loyalty"],"operationId":"listStampCards","responses":{"200":{"description":"Stamp cards","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/StampCard"}}}}}}}}},"post":{"summary":"Create a stamp card (creates its stamp counter and publishes its offer)","tags":["Loyalty"],"operationId":"createStampCard","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCardInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCard"}}}},"409":{"description":"Code already used","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Invalid card","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/stamp-cards/{programId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"programId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get a stamp card","tags":["Loyalty"],"operationId":"getStampCard","responses":{"200":{"description":"Stamp card","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCard"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update a stamp card (publishes a new version of its offer)","tags":["Loyalty"],"operationId":"updateStampCard","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCardInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCard"}}}},"422":{"description":"Invalid card","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/stamp-cards/{programId}/status":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"programId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Pause, resume or archive a stamp card (suspends or resumes its offer)","tags":["Loyalty"],"operationId":"setStampCardStatus","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramStatusChange"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCard"}}}}}}},"/workspaces/{ws}/customers/{customerId}/stamp-cards":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"summary":"Stamp cards of a customer (stamps on the current card, full cards so far)","tags":["Loyalty"],"operationId":"listCustomerStampCards","responses":{"200":{"description":"Cards","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CustomerStampCard"}}}}}}}}}},"/workspaces/{ws}/reward-actions":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"List rewarded actions","tags":["Loyalty"],"operationId":"listRewardActions","responses":{"200":{"description":"Rewarded actions","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RewardAction"}}}}}}}}},"post":{"summary":"Create a rewarded action (publishes the offer triggered by it)","tags":["Loyalty"],"operationId":"createRewardAction","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardActionInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardAction"}}}},"409":{"description":"Code already used","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Invalid action","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/reward-actions/{programId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"programId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get a rewarded action","tags":["Loyalty"],"operationId":"getRewardAction","responses":{"200":{"description":"Rewarded action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardAction"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update a rewarded action (publishes a new version of its offer)","tags":["Loyalty"],"operationId":"updateRewardAction","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardActionInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardAction"}}}},"422":{"description":"Invalid action","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/reward-actions/{programId}/status":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"programId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Pause, resume or archive a rewarded action","tags":["Loyalty"],"operationId":"setRewardActionStatus","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramStatusChange"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardAction"}}}}}}},"/workspaces/{ws}/customers/{customerId}/reward-actions":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"post":{"summary":"Record a rewarded action for a customer (sent to the engine, POST /v1/actions)","tags":["Loyalty"],"operationId":"recordCustomerAction","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action","reference"],"properties":{"action":{"type":"string"},"reference":{"type":"string","minLength":1,"maxLength":128}}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionOutcome"}}}},"422":{"description":"Unknown action or invalid reference","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/reward-items":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Rewards shop catalogue","tags":["Loyalty"],"operationId":"listRewardItems","responses":{"200":{"description":"Rewards","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RewardItem"}}}}}}}}},"post":{"summary":"Add a reward to the shop","tags":["Loyalty"],"operationId":"createRewardItem","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardItemInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardItem"}}}},"409":{"description":"Code already used","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Invalid reward","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/reward-items/{itemId}":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"itemId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get a reward","tags":["Loyalty"],"operationId":"getRewardItem","responses":{"200":{"description":"Reward","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardItem"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update a reward","tags":["Loyalty"],"operationId":"updateRewardItem","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardItemInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardItem"}}}},"422":{"description":"Invalid reward","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/reward-redemptions":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Rewards exchanged (latest first)","tags":["Loyalty"],"operationId":"listRewardRedemptions","parameters":[{"name":"customerId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"itemId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Redemptions","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RewardRedemption"}}}}}}}}}},"/workspaces/{ws}/reward-redemptions/{redemptionId}/cancel":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"name":"redemptionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Cancel an exchange (the coupon is cancelled, the points are given back)","tags":["Loyalty"],"operationId":"cancelRewardRedemption","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardRedemption"}}}},"409":{"description":"Already cancelled, or the coupon was used","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/customers/{customerId}/rewards":{"parameters":[{"$ref":"#/components/parameters/Ws"},{"$ref":"#/components/parameters/CustomerId"}],"get":{"summary":"Rewards shop seen by a customer (balance, rewards within reach)","tags":["Loyalty"],"operationId":"listCustomerRewards","responses":{"200":{"description":"Rewards","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CustomerReward"}}}}}}}}},"post":{"summary":"Exchange points for a reward (debits the points, issues the coupon)","tags":["Loyalty"],"operationId":"redeemReward","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemId"],"properties":{"itemId":{"type":"string","format":"uuid"},"channel":{"type":"string","enum":["studio","pos","app","web"]},"storeCode":{"type":"string"}}}}}},"responses":{"201":{"description":"Exchanged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardRedemption"}}}},"409":{"description":"Not enough points, out of stock, or limit reached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/workspaces/{ws}/pos/context":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"get":{"summary":"Demo POS context (stores, protocols, counters); requires features.pos_demo","tags":["POS"],"operationId":"getPosContext","responses":{"200":{"description":"Context","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosContext"}}}}}}},"/workspaces/{ws}/pos/tickets":{"parameters":[{"$ref":"#/components/parameters/Ws"}],"post":{"summary":"Send a ticket from the demo POS to the engine, with the Remizo or the Beone (WsOne) protocol","tags":["POS"],"operationId":"sendPosTicket","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosTicket"}}}},"responses":{"200":{"description":"Exchange and receipt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosTicketResult"}}}},"422":{"description":"Invalid ticket","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Engine unreachable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"securitySchemes":{"Bearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Keycloak access token, realm `studio` (client users) or `admin` (staff). Audience must contain `remizo-hub`. Studio tokens carry the `organization` claim (object keyed by organization alias = workspace slug)."},"ConnectorSecret":{"type":"apiKey","in":"header","name":"X-Connector-Secret","description":"Per-connector inbound secret (push connectors). Hashed in `sync_connectors.inbound_secret_hash`."}},"parameters":{"Ws":{"name":"ws","in":"path","required":true,"schema":{"$ref":"#/components/schemas/Slug"},"description":"Workspace slug."},"Node":{"name":"node","in":"path","required":true,"schema":{"type":"integer","minimum":1},"description":"Component index (`remizo-<ws>-pg-<node>`, `remizo-<ws>-engine-<node>`)."},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from `nextCursor`."},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","schema":{"type":"string","minLength":8,"maxLength":128}},"From":{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},"To":{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},"StoreFilter":{"name":"storeCode","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":false},"JobId":{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"ConnectorId":{"name":"connectorId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"OfferId":{"name":"offerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"TemplateId":{"name":"templateId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"CampaignId":{"name":"campaignId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"CouponId":{"name":"couponId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"CustomerId":{"name":"customerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"SegmentId":{"name":"segmentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"TestId":{"name":"testId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"WebhookId":{"name":"webhookId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}},"responses":{"Unauthorized":{"description":"Missing/invalid token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Forbidden":{"description":"Workspace not in token, or role lacks permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Conflict":{"description":"State conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unprocessable":{"description":"Validation error (`errors[]`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotImplemented":{"description":"Feature present in the UI but not implemented","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://remizo.dev/problems/not-implemented","title":"Not implemented","status":501,"code":"not-implemented","detail":"AI features are not available yet."}}}}},"schemas":{"ProgramStatusChange":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["active","paused","archived"]}}},"ProgramReward":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["coupon","points"]},"templateId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1,"maximum":10},"counterTypeCode":{"type":"string"},"amount":{"type":"integer","minimum":1},"label":{"type":"string","maxLength":120}}},"StampCardDesign":{"type":"object","properties":{"title":{"type":"string","maxLength":60},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"accent":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"icon":{"type":"string","maxLength":32}}},"StampCardInput":{"type":"object","required":["code","name","stampsRequired","reward"],"properties":{"code":{"type":"string","pattern":"^[A-Z0-9][A-Z0-9_-]{1,23}$"},"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","maxLength":500},"stampsRequired":{"type":"integer","minimum":2,"maximum":50},"stampsPerVisit":{"type":"integer","minimum":1,"maximum":10},"minAmountCents":{"type":["integer","null"],"minimum":0,"description":"Minimum ticket amount for a stamp."},"productListId":{"type":["string","null"],"format":"uuid","description":"The ticket must hold an article of this list."},"storeGroups":{"type":"array","items":{"type":"string"}},"validTo":{"type":["string","null"],"format":"date-time"},"reward":{"$ref":"#/components/schemas/ProgramReward"},"design":{"$ref":"#/components/schemas/StampCardDesign"}}},"StampCard":{"allOf":[{"$ref":"#/components/schemas/StampCardInput"},{"type":"object","required":["id","status","counterTypeCode","stats","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["active","paused","archived"]},"offerId":{"type":["string","null"],"format":"uuid"},"offerCode":{"type":["string","null"]},"counterTypeCode":{"type":"string"},"stats":{"type":"object","properties":{"customers":{"type":"integer","description":"Customers with at least one stamp."},"stamps":{"type":"integer","description":"Stamps given."},"completed":{"type":"integer","description":"Full cards."}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"CustomerStampCard":{"type":"object","required":["programId","code","name","status","stamps","stampsRequired","completed"],"properties":{"programId":{"type":"string","format":"uuid"},"code":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"stamps":{"type":"integer","description":"Stamps on the current card."},"stampsRequired":{"type":"integer"},"completed":{"type":"integer","description":"Full cards so far."},"lastStampAt":{"type":["string","null"],"format":"date-time"},"rewardLabel":{"type":["string","null"]},"design":{"$ref":"#/components/schemas/StampCardDesign"}}},"RewardActionInput":{"type":"object","required":["code","name","reward"],"properties":{"code":{"type":"string","pattern":"^[A-Z0-9][A-Z0-9_-]{1,23}$","description":"Action code sent by the apps (POST /v1/actions)."},"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","maxLength":500},"reward":{"$ref":"#/components/schemas/ProgramReward"},"maxPerCustomer":{"type":["integer","null"],"minimum":1,"description":"Rewards per customer and period (none = unlimited)."},"period":{"type":"string","enum":["ever","day","week","month","year"],"default":"ever"},"validTo":{"type":["string","null"],"format":"date-time"}}},"RewardAction":{"allOf":[{"$ref":"#/components/schemas/RewardActionInput"},{"type":"object","required":["id","status","stats","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["active","paused","archived"]},"offerId":{"type":["string","null"],"format":"uuid"},"offerCode":{"type":["string","null"]},"stats":{"type":"object","properties":{"received":{"type":"integer","description":"Actions received."},"rewarded":{"type":"integer","description":"Actions rewarded."},"customers":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"ActionOutcome":{"type":"object","required":["rewarded","replay"],"properties":{"rewarded":{"type":"boolean"},"replay":{"type":"boolean","description":"The reference was already received; nothing new was granted."},"reason":{"type":["string","null"],"description":"Why nothing was granted (limit reached…)."},"points":{"type":"array","items":{"type":"object","properties":{"counterTypeCode":{"type":"string"},"amount":{"type":"integer"}}}},"coupons":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"templateName":{"type":"string"},"validTo":{"type":"string"}}}}}},"RewardItemInput":{"type":"object","required":["code","name","cost","counterTypeCode","couponTemplateId"],"properties":{"code":{"type":"string","pattern":"^[A-Z0-9][A-Z0-9_-]{1,31}$"},"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":["string","null"],"maxLength":500},"imageUrl":{"type":["string","null"],"maxLength":1000},"cost":{"type":"integer","minimum":1},"counterTypeCode":{"type":"string"},"couponTemplateId":{"type":"string","format":"uuid"},"validityDays":{"type":["integer","null"],"minimum":1,"description":"Validity of the coupon (default the template's)."},"stock":{"type":["integer","null"],"minimum":0},"maxPerCustomer":{"type":["integer","null"],"minimum":1},"active":{"type":"boolean"},"availableFrom":{"type":["string","null"],"format":"date-time"},"availableTo":{"type":["string","null"],"format":"date-time"},"sort":{"type":"integer"}}},"RewardItem":{"allOf":[{"$ref":"#/components/schemas/RewardItemInput"},{"type":"object","required":["id","templateName","redeemed","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"templateName":{"type":"string"},"counterUnit":{"type":"string"},"redeemed":{"type":"integer","description":"Exchanges not cancelled."},"remaining":{"type":["integer","null"],"description":"Stock left."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"CustomerReward":{"allOf":[{"$ref":"#/components/schemas/RewardItem"},{"type":"object","required":["balance","affordable","available"],"properties":{"balance":{"type":"integer","description":"Points available to the customer on the counter."},"affordable":{"type":"boolean"},"available":{"type":"boolean","description":"Active, in stock, within dates and below the customer limit."},"unavailableReason":{"type":["string","null"],"enum":["inactive","out_of_stock","not_yet","ended","limit_reached",null]},"redeemedByCustomer":{"type":"integer"}}}]},"RewardRedemption":{"type":"object","required":["id","itemId","itemName","customerId","cost","counterTypeCode","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"itemId":{"type":"string","format":"uuid"},"itemName":{"type":"string"},"customerId":{"type":"string","format":"uuid"},"customerName":{"type":["string","null"]},"cost":{"type":"integer"},"counterTypeCode":{"type":"string"},"status":{"type":"string","enum":["done","cancelled"]},"coupon":{"type":["object","null"],"properties":{"id":{"type":"string"},"code":{"type":"string"},"state":{"type":"string"},"validTo":{"type":"string"}}},"channel":{"type":["string","null"]},"storeCode":{"type":["string","null"]},"actorId":{"type":["string","null"]},"cancelReason":{"type":["string","null"]},"cancelledAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"PosContext":{"type":"object","required":["stores","protocols","counterTypes","currency"],"properties":{"workspaceName":{"type":"string"},"currency":{"type":"string"},"stores":{"type":"array","items":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}}},"protocols":{"type":"object","properties":{"remizo":{"type":"boolean"},"beone":{"type":"boolean","description":"WsOne adapter enabled on the workspace."}}},"counterTypes":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"unit":{"type":"string"},"kind":{"type":"string"}}}}}},"PosLine":{"type":"object","required":["name","qty","unitPriceCents"],"properties":{"ref":{"type":"string"},"ean":{"type":"string"},"name":{"type":"string"},"qty":{"type":"number","exclusiveMinimum":0},"unitPriceCents":{"type":"integer","minimum":0},"department":{"type":"string"},"family":{"type":"string"},"subfamily":{"type":"string"},"brand":{"type":"string"}}},"PosTicket":{"type":"object","required":["protocol","mode","storeCode","lines"],"properties":{"protocol":{"type":"string","enum":["remizo","beone"]},"mode":{"type":"string","enum":["simulate","commit"],"description":"simulate = nothing recorded (Remizo simulate, WsOne verifyTransaction)."},"storeCode":{"type":"string"},"posId":{"type":"string","maxLength":32},"customer":{"type":["object","null"],"properties":{"type":{"type":"string"},"value":{"type":"string"}}},"lines":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/components/schemas/PosLine"}},"coupons":{"type":"array","items":{"type":"string"}}}},"PosTicketResult":{"type":"object","required":["protocol","mode","ok","httpStatus","durationMs","request","response","receipt"],"properties":{"protocol":{"type":"string","enum":["remizo","beone"]},"mode":{"type":"string"},"ok":{"type":"boolean"},"error":{"type":["string","null"]},"endpoint":{"type":"string"},"httpStatus":{"type":"integer"},"durationMs":{"type":"integer"},"ticketNumber":{"type":"string"},"request":{"description":"Body sent to the engine."},"response":{"description":"Body returned by the engine."},"receipt":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"lineNo":{"type":"integer"},"name":{"type":"string"},"qty":{"type":"number"},"unitPriceCents":{"type":"integer"},"totalCents":{"type":"integer"},"discountCents":{"type":"integer"}}}},"grossCents":{"type":"integer"},"discountCents":{"type":"integer"},"netCents":{"type":"integer"},"offers":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string","enum":["discount","points","coupon","message","stamp_card","other"]},"valueCents":{"type":"integer"},"amount":{"type":"integer"},"unit":{"type":"string"},"couponCode":{"type":"string"},"message":{"type":"string"},"stampCard":{"type":"object","properties":{"stampsRequired":{"type":"integer"},"stampsBefore":{"type":"integer"},"stampsAdded":{"type":"integer"},"stampsAfter":{"type":"integer"},"completed":{"type":"integer"}}}}}},"balances":{"type":"array","items":{"type":"object","properties":{"counterTypeCode":{"type":"string"},"name":{"type":"string"},"unit":{"type":"string"},"before":{"type":"integer"},"after":{"type":"integer"}}}},"couponsIssued":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"validTo":{"type":"string"}}}},"couponsUsed":{"type":"array","items":{"type":"string"}},"messages":{"type":"array","items":{"type":"string"}},"customer":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"cardNumber":{"type":"string"}}}}}}},"Slug":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,30}[a-z0-9]$","description":"Workspace slug (immutable, used in container names)."},"Problem":{"type":"object","description":"RFC 9457. `type` = `https://remizo.dev/problems/<code>`.","required":["type","title","status","code"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"code":{"type":"string","enum":["validation-failed","unauthorized","forbidden","workspace-not-in-token","not-found","conflict","precondition-failed","idempotency-key-reused","state-transition-invalid","last-owner","engine-unreachable","not-implemented","feature-disabled","rate-limited","internal"]},"traceId":{"type":"string"},"errors":{"type":"array","items":{"type":"object","required":["path","message"],"properties":{"path":{"type":"string"},"message":{"type":"string"},"rule":{"type":"string"}}}}},"additionalProperties":true},"Page":{"type":"object","required":["items","nextCursor"],"properties":{"items":{"type":"array","items":{}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to get the next page; null when exhausted."},"total":{"type":["integer","null"],"description":"Exact or estimated total when cheap to compute."}}},"Severity":{"type":"string","enum":["info","warning","critical"]},"SignedLink":{"type":"object","required":["url","expiresAt"],"properties":{"url":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time"},"sizeBytes":{"type":"integer"},"filename":{"type":"string"}}},"ImportSummary":{"type":"object","required":["received","accepted","rejected"],"properties":{"received":{"type":"integer"},"accepted":{"type":"integer"},"inserted":{"type":"integer"},"updated":{"type":"integer"},"rejected":{"type":"integer"},"rejects":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/SyncReject"}},"jobId":{"type":"string","format":"uuid"}}},"LogLine":{"type":"object","properties":{"ts":{"type":"string","format":"date-time"},"level":{"type":"string","enum":["debug","info","warn","error"]},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":true}}},"Me":{"type":"object","required":["sub","realm","email","workspaces","preferences"],"properties":{"sub":{"type":"string"},"realm":{"type":"string","enum":["studio","admin"]},"email":{"type":"string","format":"email"},"name":{"type":"string"},"staffRoles":{"type":"array","items":{"$ref":"#/components/schemas/StaffRole"}},"mfaEnrolled":{"type":"boolean"},"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceSummary"}},"defaultWorkspace":{"type":["string","null"]},"preferences":{"$ref":"#/components/schemas/UserPreferences"}}},"UserPreferences":{"type":"object","properties":{"locale":{"type":"string","enum":["fr","en"]},"theme":{"type":"string","enum":["light","dark","system"]},"defaultWorkspace":{"$ref":"#/components/schemas/Slug"}}},"StaffRole":{"type":"string","enum":["platform_admin","platform_operator","platform_support"]},"WorkspaceRole":{"type":"string","description":"owner > admin > approver / marketer / developer / support / analyst > viewer. Permissions in `GET /workspaces/{ws}/roles`.","enum":["owner","admin","marketer","approver","developer","support","analyst","viewer"]},"RoleDefinition":{"type":"object","required":["role","permissions"],"properties":{"role":{"$ref":"#/components/schemas/WorkspaceRole"},"description":{"type":"string"},"permissions":{"type":"array","items":{"type":"string","description":"`<resource>:<action>`","enum":["offers:read","offers:write","offers:submit","offers:approve","offers:publish","offers:suspend","simulator:use","coupons:read","coupons:write","customers:read","customers:write","customers:adjust","customers:merge","customers:anonymize","customers:import","counters:write","segments:read","segments:write","abtests:write","stats:read","audit:read","members:read","members:write","apikeys:read","apikeys:write","webhooks:write","adapters:write","sync:read","sync:write","settings:write","theme:write","backups:read","cron:read","cron:schedule","ai:use"]}}}},"WorkspaceSummary":{"type":"object","required":["slug","name","kind","status"],"properties":{"slug":{"$ref":"#/components/schemas/Slug"},"name":{"type":"string"},"kind":{"$ref":"#/components/schemas/WorkspaceKind"},"status":{"$ref":"#/components/schemas/WorkspaceStatus"},"role":{"$ref":"#/components/schemas/WorkspaceRole"},"logoUrl":{"type":["string","null"]},"primaryColor":{"type":"string"}}},"WorkspaceContext":{"type":"object","required":["workspace","role","permissions","features"],"properties":{"workspace":{"$ref":"#/components/schemas/Workspace"},"role":{"$ref":"#/components/schemas/WorkspaceRole"},"permissions":{"type":"array","items":{"type":"string"}},"features":{"type":"object","additionalProperties":{"type":"boolean"}},"theme":{"$ref":"#/components/schemas/WorkspaceTheme"},"engineBaseUrl":{"type":"string","format":"uri","description":"Public engine URL to display in integration pages."}}},"WorkspaceStatus":{"type":"string","enum":["provisioning","active","degraded","suspended","deleting","deleted","failed"]},"WorkspaceKind":{"type":"string","enum":["production","sandbox","restore"]},"WorkspaceMode":{"type":"string","enum":["autonomous","synced"],"description":"autonomous = Remizo is master of customer data; synced = external CRM is master."},"Workspace":{"type":"object","required":["id","slug","name","kind","mode","status","currency","timezone","locale","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"slug":{"$ref":"#/components/schemas/Slug"},"name":{"type":"string"},"kind":{"$ref":"#/components/schemas/WorkspaceKind"},"parentSlug":{"type":["string","null"],"description":"For sandbox/restore workspaces."},"mode":{"$ref":"#/components/schemas/WorkspaceMode"},"status":{"$ref":"#/components/schemas/WorkspaceStatus"},"currency":{"type":"string","pattern":"^[A-Z]{3}$"},"timezone":{"type":"string"},"locale":{"type":"string","enum":["fr","en"]},"keycloakOrgId":{"type":"string"},"spec":{"$ref":"#/components/schemas/WorkspaceSpec"},"features":{"type":"object","additionalProperties":{"type":"boolean"}},"engineUrl":{"type":"string","format":"uri"},"logoUrl":{"type":["string","null"],"description":"From the workspace theme."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"WorkspaceCreate":{"type":"object","required":["slug","name","mode","currency","timezone","locale","ownerEmail","spec"],"properties":{"slug":{"$ref":"#/components/schemas/Slug"},"name":{"type":"string"},"mode":{"$ref":"#/components/schemas/WorkspaceMode"},"currency":{"type":"string"},"timezone":{"type":"string","default":"Europe/Paris"},"locale":{"type":"string","enum":["fr","en"],"default":"fr"},"ownerEmail":{"type":"string","format":"email","description":"First member, role owner."},"spec":{"$ref":"#/components/schemas/WorkspaceSpec"},"features":{"type":"object","additionalProperties":{"type":"boolean"}},"createSandbox":{"type":"boolean","default":true}}},"WorkspaceUpdate":{"type":"object","properties":{"name":{"type":"string"},"mode":{"$ref":"#/components/schemas/WorkspaceMode"},"timezone":{"type":"string"},"locale":{"type":"string"},"spec":{"$ref":"#/components/schemas/WorkspaceSpec"},"status":{"type":"string","enum":["active","suspended"]}}},"WorkspaceSpec":{"type":"object","description":"Single description of a workspace deployment, rendered by the driver (compose | swarm | k8s) into concrete manifests.","required":["driver","slot","engines","postgres"],"properties":{"driver":{"type":"string","enum":["compose","swarm","k8s"]},"slot":{"type":"integer","minimum":0,"maximum":19,"description":"Port slot: host ports = 47500 + slot*20 + offset (see architecture.md)."},"engines":{"type":"object","required":["replicas"],"properties":{"replicas":{"type":"integer","minimum":1,"maximum":32},"image":{"type":"string"},"cpu":{"type":"string","example":"1.0"},"memory":{"type":"string","example":"512Mi"},"serverIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},"postgres":{"type":"object","required":["instances"],"properties":{"instances":{"type":"integer","minimum":1,"maximum":5},"storage":{"type":"string","example":"20Gi"},"version":{"type":"string","default":"17"},"ha":{"type":"string","enum":["none","patroni","cnpg"],"default":"none"},"serverIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},"valkey":{"type":"object","properties":{"enabled":{"type":"boolean","default":true},"memory":{"type":"string"}}},"network":{"type":"object","properties":{"publicHost":{"type":"string","description":"Traefik host for the engine, e.g. acme.engine.remizo.dev"}}},"silo":{"type":"object","properties":{"bucket":{"type":"string","description":"Default mo-<slug>"}}},"labels":{"type":"object","additionalProperties":{"type":"string"}}}},"ProvisioningStatus":{"type":"object","required":["status","steps"],"properties":{"status":{"type":"string","enum":["pending","running","succeeded","failed"]},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"steps":{"type":"array","items":{"type":"object","required":["code","status"],"properties":{"code":{"type":"string","enum":["keycloak_org","openbao_path","network","postgres","pg_ha","migrations","seed","valkey","engines","silo_bucket","clickhouse","rabbitmq","traefik","api_key","health_check","sandbox"]},"status":{"type":"string","enum":["pending","running","succeeded","failed","skipped"]},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"message":{"type":"string"}}}}}},"WorkspaceSettings":{"type":"object","description":"Business/engine settings. Stored in the workspace DB `settings` (key/value) — keys below.","properties":{"transactions":{"type":"object","properties":{"pendingTtlSeconds":{"type":"integer","default":86400},"cancelWindowSeconds":{"type":"integer","default":86400},"coherenceCheck":{"type":"boolean","default":true},"reevaluateOnPayments":{"type":"boolean","default":false},"explainByDefault":{"type":"boolean","default":true}}},"customers":{"type":"object","properties":{"autoCreateCard":{"type":"boolean","default":true},"cardNumberPattern":{"type":"string"},"allowManualCreate":{"type":"boolean"},"editableFields":{"type":"array","items":{"type":"string"}}}},"points":{"type":"object","properties":{"reservationTtlSeconds":{"type":"integer","default":900},"expiryWarningDays":{"type":"integer","default":30}}},"coupons":{"type":"object","properties":{"defaultFormat":{"type":"string","enum":["ean13","qr","short"]},"ean13Prefix":{"type":"string","pattern":"^[0-9]{2,4}$"},"renderLinkTtlDays":{"type":"integer","default":90}}},"workflow":{"type":"object","properties":{"requireApproval":{"type":"boolean","default":true},"allowSelfApproval":{"type":"boolean","default":false}}},"stacking":{"type":"object","properties":{"maxLevel":{"type":"integer","default":9},"levelLabels":{"type":"object","additionalProperties":{"type":"string"}}}},"retention":{"type":"object","properties":{"transactionsDays":{"type":"integer","default":730},"auditDays":{"type":"integer","default":1825},"anonymizeInactiveAfterDays":{"type":["integer","null"]}}},"locales":{"type":"object","properties":{"supported":{"type":"array","items":{"type":"string"}},"default":{"type":"string"}}}}},"WorkspaceTheme":{"type":"object","properties":{"primaryColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","default":"#FF8500"},"logoUrl":{"type":["string","null"]},"logoDarkUrl":{"type":["string","null"]},"faviconUrl":{"type":["string","null"]},"fontFamily":{"type":"string","default":"Poppins"},"defaultMode":{"type":"string","enum":["light","dark","system"]},"cssOverrides":{"type":"object","additionalProperties":{"type":"string"},"description":"Design-token overrides (--color-*, --radius-*)."}}},"Store":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}},"region":{"type":"string"},"timezone":{"type":"string"},"address":{"type":"object","additionalProperties":true},"attributes":{"type":"object","additionalProperties":true},"active":{"type":"boolean","default":true}}},"Article":{"type":"object","required":["ref","label","active","attributes","image","createdAt","updatedAt"],"properties":{"ref":{"type":"string","description":"Article reference (SKU), the key."},"ean":{"type":["string","null"]},"label":{"type":"string"},"department":{"type":["string","null"],"description":"Rayon."},"family":{"type":["string","null"]},"subfamily":{"type":["string","null"]},"brand":{"type":["string","null"]},"priceCents":{"type":["integer","null"]},"active":{"type":"boolean"},"attributes":{"type":"object","additionalProperties":true},"image":{"type":["object","null"],"required":["status","url","source","error"],"properties":{"status":{"type":"string","enum":["pending","ok","failed"]},"url":{"type":["string","null"],"description":"Hub path of the image (GET with the usual token) when status is ok."},"source":{"type":["string","null"],"description":"URL or Silo key it was taken from."},"error":{"type":["string","null"]}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ArticleFacet":{"type":"object","required":["value","count"],"properties":{"value":{"type":"string"},"count":{"type":"integer"}}},"ArticleFacets":{"type":"object","required":["total","departments","families","subfamilies","brands","images"],"properties":{"total":{"type":"integer"},"departments":{"type":"array","items":{"$ref":"#/components/schemas/ArticleFacet"}},"families":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/ArticleFacet"},{"properties":{"department":{"type":["string","null"]}}}]}},"subfamilies":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/ArticleFacet"},{"properties":{"family":{"type":["string","null"]}}}]}},"brands":{"type":"array","items":{"$ref":"#/components/schemas/ArticleFacet"}},"images":{"type":"object","required":["ok","pending","failed","none"],"properties":{"ok":{"type":"integer"},"pending":{"type":"integer"},"failed":{"type":"integer"},"none":{"type":"integer"}}}}},"StoreGroup":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"storeCodes":{"type":"array","items":{"type":"string"}}}},"PlatformOverview":{"type":"object","required":["workspaces"],"properties":{"workspaces":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"kind":{"$ref":"#/components/schemas/WorkspaceKind"},"status":{"$ref":"#/components/schemas/WorkspaceStatus"},"health":{"type":"string","enum":["green","amber","red","grey"]},"engines":{"type":"object","properties":{"ready":{"type":"integer"},"total":{"type":"integer"},"rps":{"type":"number"},"p99Ms":{"type":"number"}}},"database":{"type":"object","properties":{"primary":{"type":"string"},"replicas":{"type":"integer"},"maxLagBytes":{"type":"integer"},"diskUsedPct":{"type":"number"}}},"lastBackupAgeSeconds":{"type":["integer","null"]},"openAlerts":{"type":"integer"}}}},"servers":{"type":"array","items":{"$ref":"#/components/schemas/Server"}},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/Alert"}}}},"Server":{"type":"object","required":["id","name","driver","status"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"driver":{"type":"string","enum":["compose","swarm","k8s"]},"address":{"type":"string","description":"Docker host URL / swarm manager / kube context."},"labels":{"type":"object","additionalProperties":{"type":"string"}},"status":{"type":"string","enum":["unknown","online","offline","draining"]},"capacity":{"type":"object","properties":{"cpu":{"type":"number"},"memoryBytes":{"type":"integer"},"diskBytes":{"type":"integer"}}},"usage":{"type":"object","properties":{"cpu":{"type":"number"},"memoryBytes":{"type":"integer"},"diskBytes":{"type":"integer"},"containers":{"type":"integer"}}},"lastProbeAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"ServerCreate":{"type":"object","required":["name","driver","address"],"properties":{"name":{"type":"string"},"driver":{"type":"string","enum":["compose","swarm","k8s"]},"address":{"type":"string"},"credentialsSecretPath":{"type":"string","description":"OpenBao path holding TLS certs / kubeconfig, e.g. mo/servers/<id>"},"labels":{"type":"object","additionalProperties":{"type":"string"}}}},"ServerProbe":{"type":"object","properties":{"reachable":{"type":"boolean"},"driverVersion":{"type":"string"},"latencyMs":{"type":"number"},"capacity":{"type":"object","additionalProperties":true},"errors":{"type":"array","items":{"type":"string"}}}},"InfraStatus":{"type":"object","required":["workspace","databases","engines"],"properties":{"workspace":{"$ref":"#/components/schemas/Slug"},"collectedAt":{"type":"string","format":"date-time"},"databases":{"type":"array","items":{"type":"object","required":["node","container","role","state"],"properties":{"node":{"type":"integer"},"container":{"type":"string","example":"remizo-acme-pg-1"},"serverId":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["primary","replica","unknown"]},"state":{"type":"string","enum":["running","starting","stopped","failed"]},"lagBytes":{"type":["integer","null"]},"lagSeconds":{"type":["number","null"]},"diskUsedBytes":{"type":"integer"},"diskTotalBytes":{"type":"integer"},"connections":{"type":"integer"},"version":{"type":"string"},"uptimeSeconds":{"type":"integer"}}}},"haproxy":{"type":"object","properties":{"container":{"type":"string"},"state":{"type":"string"},"rwTarget":{"type":"string"},"roTargets":{"type":"array","items":{"type":"string"}}}},"valkey":{"type":"object","properties":{"container":{"type":"string"},"state":{"type":"string"},"memoryUsedBytes":{"type":"integer"},"hitRate":{"type":"number"}}},"engines":{"type":"array","items":{"type":"object","required":["node","container","state"],"properties":{"node":{"type":"integer"},"container":{"type":"string","example":"remizo-acme-engine-1"},"serverId":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["running","starting","draining","stopped","failed"]},"ready":{"type":"boolean"},"version":{"type":"string"},"offersLoaded":{"type":"integer"},"offersLoadedAt":{"type":"string","format":"date-time"},"rps":{"type":"number"},"p99Ms":{"type":"number"},"errorRate":{"type":"number"},"uptimeSeconds":{"type":"integer"}}}},"queues":{"type":"object","properties":{"control":{"type":"object","properties":{"name":{"type":"string"},"ready":{"type":"integer"},"consumers":{"type":"integer"}}},"outboxPending":{"type":"integer"}}},"pendingOperations":{"type":"array","items":{"$ref":"#/components/schemas/Operation"}}}},"Operation":{"type":"object","required":["id","kind","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["db_restart","db_switchover","db_add_replica","db_remove_replica","engine_restart","engine_scale","move_placement","provision","deprovision","restore"]},"status":{"type":"string","enum":["queued","running","succeeded","failed","cancelled"]},"target":{"type":"string"},"requestedBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"progress":{"type":"array","items":{"$ref":"#/components/schemas/LogLine"}},"error":{"type":["string","null"]}}},"Placement":{"type":"object","required":["component","node","serverId"],"properties":{"component":{"type":"string","enum":["engine","pg","haproxy","etcd","valkey"]},"node":{"type":"integer"},"serverId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["planned","running","moving","failed"]}}},"BackupKind":{"type":"string","enum":["pg_full","pg_wal","hub_pg","keycloak","openbao","silo"]},"BackupStatus":{"type":"string","enum":["running","succeeded","failed","expired","deleted"]},"BackupPolicy":{"type":"object","required":["fullCron","continuous","retention"],"properties":{"fullCron":{"type":"string","default":"0 2 * * *"},"continuous":{"type":"boolean","default":true,"description":"WAL archiving every 60 s."},"retention":{"type":"object","properties":{"pitrDays":{"type":"integer","default":7,"description":"Point-in-time window."},"dailyDays":{"type":"integer","default":30},"weeklyWeeks":{"type":"integer","default":4},"monthlyMonths":{"type":"integer","default":12}}},"restoreTestCron":{"type":["string","null"],"default":"0 4 * * 0"},"destination":{"type":"object","properties":{"bucket":{"type":"string"},"prefix":{"type":"string"},"objectLock":{"type":"boolean"}}},"alerts":{"type":"object","properties":{"maxAgeHours":{"type":"integer","default":26},"minFreeBytes":{"type":"integer"}}},"includeSilo":{"type":"boolean","default":true}}},"Backup":{"type":"object","required":["id","workspace","kind","status","startedAt"],"properties":{"id":{"type":"string","format":"uuid"},"workspace":{"type":["string","null"]},"kind":{"$ref":"#/components/schemas/BackupKind"},"label":{"type":"string"},"status":{"$ref":"#/components/schemas/BackupStatus"},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"durationSeconds":{"type":["number","null"]},"sizeBytes":{"type":["integer","null"]},"location":{"type":"string","description":"s3://<bucket>/<key>"},"checksum":{"type":"string"},"pitrFrom":{"type":["string","null"],"format":"date-time"},"pitrTo":{"type":["string","null"],"format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time"},"lastTest":{"type":["object","null"],"properties":{"at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["succeeded","failed"]},"durationSeconds":{"type":"number"},"message":{"type":"string"}}},"cronRunId":{"type":["string","null"],"format":"uuid"},"error":{"type":["string","null"]}}},"BackupSummary":{"type":"object","properties":{"workspace":{"type":"string"},"lastSuccessAt":{"type":["string","null"],"format":"date-time"},"lastSuccessAgeSeconds":{"type":["integer","null"]},"lastStatus":{"$ref":"#/components/schemas/BackupStatus"},"pitrWindow":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}},"totalBytes":{"type":"integer"},"count":{"type":"integer"},"trend30d":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"bytes":{"type":"integer"}}}},"untestedCount":{"type":"integer"},"lastTestAt":{"type":["string","null"],"format":"date-time"}}},"BackupStorage":{"type":"object","properties":{"endpoint":{"type":"string"},"bucket":{"type":"string"},"usedBytes":{"type":"integer"},"capacityBytes":{"type":["integer","null"]},"objectLock":{"type":"boolean"}}},"RestoreRequest":{"type":"object","required":["target"],"properties":{"backupId":{"type":"string","format":"uuid"},"targetTime":{"type":"string","format":"date-time","description":"PITR target (alternative to backupId)."},"target":{"type":"string","enum":["in_place","test_workspace"]},"testWorkspaceSlug":{"$ref":"#/components/schemas/Slug"},"confirmSlug":{"type":"string","description":"Required for in_place."},"includeSilo":{"type":"boolean","default":false},"reason":{"type":"string"}}},"Restore":{"type":"object","required":["id","status","target","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"workspace":{"type":"string"},"backupId":{"type":["string","null"],"format":"uuid"},"targetTime":{"type":["string","null"],"format":"date-time"},"target":{"type":"string","enum":["in_place","test_workspace","test_container"]},"targetWorkspace":{"type":["string","null"]},"status":{"type":"string","enum":["queued","running","succeeded","failed","cancelled"]},"requestedBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"log":{"type":"array","items":{"$ref":"#/components/schemas/LogLine"}},"checks":{"type":"object","additionalProperties":{"type":"string"}}}},"RunStatus":{"type":"string","enum":["queued","running","succeeded","failed","timed_out","cancelled","skipped"]},"CronJobCode":{"type":"string","description":"Built-in job codes. One row per (code, workspace) — workspace null for global jobs.","enum":["backups.full","backups.wal","backups.restore_test","backups.prune","backups.hub","backups.keycloak","backups.openbao","coupons.expire","points.expire","points.expiry_warnings","points.reservations.expire","points.activate","counters.periodic_reset","transactions.expire_pending","sync.run","stats.aggregate","stats.rollup_daily","gdpr.retention","gdpr.anonymize_inactive","silo.cleanup","segments.compute","customers.birthdays","offers.expire","abtests.close","webhooks.retry","infra.probe","alerts.evaluate"]},"CronJob":{"type":"object","required":["id","code","name","schedule","timezone","enabled","scope"],"properties":{"id":{"type":"string","format":"uuid"},"code":{"$ref":"#/components/schemas/CronJobCode"},"name":{"type":"string"},"scope":{"type":"string","enum":["global","workspace"]},"workspace":{"type":["string","null"]},"schedule":{"type":"string","description":"5-field cron."},"scheduleDescription":{"type":"string"},"timezone":{"type":"string"},"enabled":{"type":"boolean"},"timeoutSeconds":{"type":"integer"},"maxRetries":{"type":"integer"},"singleton":{"type":"boolean","default":true,"description":"Never two concurrent runs (pg-boss singleton key)."},"payload":{"type":"object","additionalProperties":true},"editableByWorkspace":{"type":"boolean","description":"Business jobs schedulable from the studio."},"lastRun":{"$ref":"#/components/schemas/CronRun"},"nextRunAt":{"type":["string","null"],"format":"date-time"},"stats7d":{"type":"object","properties":{"runs":{"type":"integer"},"failures":{"type":"integer"},"avgDurationSeconds":{"type":"number"}}}}},"CronJobUpdate":{"type":"object","properties":{"schedule":{"type":"string"},"timezone":{"type":"string"},"enabled":{"type":"boolean"},"timeoutSeconds":{"type":"integer","minimum":10},"maxRetries":{"type":"integer","minimum":0,"maximum":10},"payload":{"type":"object","additionalProperties":true}}},"CronRun":{"type":"object","required":["id","jobId","status","startedAt"],"properties":{"id":{"type":"string","format":"uuid"},"jobId":{"type":"string","format":"uuid"},"code":{"type":"string"},"workspace":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/RunStatus"},"trigger":{"type":"string","enum":["schedule","manual","retry","api"]},"attempt":{"type":"integer"},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"durationSeconds":{"type":["number","null"]},"hubInstance":{"type":"string"},"output":{"type":"object","additionalProperties":true},"error":{"type":["string","null"]}}},"SyncConnectorType":{"type":"string","enum":["push_api","push_webhook","pull_sftp","pull_silo","pull_api"]},"SyncEntity":{"type":"string","enum":["customers","accounts","cards","counters","events","stores","store_groups","products","articles"],"description":"`products` fills the items of a product list; `articles` is the article catalogue."},"SyncMapping":{"type":"object","description":"Field mapping from source to Remizo entity. CEL expressions over `src` (one source record).","required":["entity","fields"],"properties":{"entity":{"$ref":"#/components/schemas/SyncEntity"},"format":{"type":"string","enum":["json","csv","xml"],"default":"json"},"csv":{"type":"object","properties":{"delimiter":{"type":"string","default":";"},"header":{"type":"boolean","default":true},"encoding":{"type":"string","default":"utf-8"}}},"recordPath":{"type":"string","description":"JSON path to the array of records."},"keyField":{"type":"string","description":"Target field used for upsert (externalRef, cardNumber, email)."},"fields":{"type":"object","additionalProperties":{"type":"string","description":"CEL expression, e.g. src.PRENOM or date(src.DOB, \"dd/MM/yyyy\")"},"description":"target field -> expression"},"validations":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"rule":{"type":"string","enum":["required","email","phone","date","regex","in"]},"value":{"type":"string"}}}},"onMissing":{"type":"string","enum":["skip","reject"],"default":"reject"},"deleteMode":{"type":"string","enum":["none","flag_inactive","anonymize"],"default":"none"}}},"SyncConnectorCreate":{"type":"object","required":["name","type","mapping"],"properties":{"name":{"type":"string"},"type":{"$ref":"#/components/schemas/SyncConnectorType"},"enabled":{"type":"boolean","default":true},"mapping":{"$ref":"#/components/schemas/SyncMapping"},"schedule":{"type":["string","null"],"description":"Cron for pull connectors."},"source":{"type":"object","description":"Pull configuration; secrets are stored in OpenBao `mo/<ws>/sync/<connectorId>` and never returned.","properties":{"sftp":{"type":"object","properties":{"host":{"type":"string"},"port":{"type":"integer","default":22},"username":{"type":"string"},"password":{"type":"string","writeOnly":true},"privateKey":{"type":"string","writeOnly":true},"path":{"type":"string"},"filePattern":{"type":"string"},"afterProcess":{"type":"string","enum":["keep","move","delete"]},"archivePath":{"type":"string"}}},"silo":{"type":"object","properties":{"bucket":{"type":"string"},"prefix":{"type":"string"},"filePattern":{"type":"string"},"afterProcess":{"type":"string","enum":["keep","move","delete"]}}},"api":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"method":{"type":"string","enum":["GET","POST"]},"auth":{"type":"string","enum":["none","basic","bearer","api_key","oauth2_client_credentials"]},"credentials":{"type":"object","writeOnly":true,"additionalProperties":true},"headers":{"type":"object","additionalProperties":{"type":"string"}},"pagination":{"type":"object","properties":{"kind":{"type":"string","enum":["none","page","cursor","since"]},"param":{"type":"string"}}},"sinceField":{"type":"string"}}}}},"alerting":{"type":"object","properties":{"onFailure":{"type":"boolean","default":true},"rejectRatePct":{"type":"number","default":5},"notify":{"$ref":"#/components/schemas/SyncNotify"}}},"listening":{"type":"boolean","default":false,"description":"Push connectors: payloads received are kept as samples (the last 5) and not imported, to build the mapping from what the sender really sends."}}},"SyncPushSample":{"type":"object","required":["id","receivedAt","contentType","size","records","body","truncated"],"properties":{"id":{"type":"string","format":"uuid"},"receivedAt":{"type":"string","format":"date-time"},"contentType":{"type":"string"},"size":{"type":"integer","description":"Size of the payload in bytes."},"records":{"type":"integer","description":"Records counted (array length, CSV lines)."},"body":{"type":"string","description":"Start of the payload (at most 256 KB), to use as the mapping sample."},"truncated":{"type":"boolean"}}},"SyncConnector":{"allOf":[{"$ref":"#/components/schemas/SyncConnectorCreate"},{"type":"object","required":["id","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"inboundUrl":{"type":["string","null"],"format":"uri","description":"Push connectors: POST /public/sync/{ws}/{connectorId}."},"secret":{"type":"string","description":"Inbound secret, only on creation."},"lastRun":{"$ref":"#/components/schemas/SyncRun"},"nextRunAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"SyncRun":{"type":"object","required":["id","connectorId","status","startedAt"],"properties":{"id":{"type":"string","format":"uuid"},"connectorId":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/RunStatus"},"trigger":{"type":"string","enum":["schedule","manual","push"]},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"files":{"type":"array","items":{"type":"string"}},"received":{"type":"integer"},"accepted":{"type":"integer"},"inserted":{"type":"integer"},"updated":{"type":"integer"},"rejected":{"type":"integer"},"error":{"type":["string","null"]},"logUrl":{"type":["string","null"],"format":"uri"},"flowRunId":{"type":["string","null"],"format":"uuid","description":"Execution of an import chain this run belongs to."},"cancelledBy":{"type":["string","null"]},"progress":{"type":["number","null"],"minimum":0,"maximum":100,"description":"Share of the run done, in % (bytes of the files read, or records processed). Null when unknown."}}},"SyncFlowGraph":{"type":"object","required":["nodes","edges"],"description":"An arrow from → to means \"to starts once from has succeeded\". Connectors with no arrow coming in start together.","properties":{"nodes":{"type":"array","maxItems":50,"items":{"type":"object","required":["connectorId"],"properties":{"connectorId":{"type":"string","format":"uuid"},"x":{"type":"number"},"y":{"type":"number"}}}},"edges":{"type":"array","maxItems":200,"items":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"uuid"},"to":{"type":"string","format":"uuid"}}}}}},"SyncFlowCreate":{"type":"object","required":["name","graph"],"properties":{"name":{"type":"string","minLength":2,"maxLength":120},"description":{"type":["string","null"]},"enabled":{"type":"boolean","default":true},"schedule":{"type":["string","null"],"description":"Cron expression; null = run by hand only."},"timezone":{"type":"string","default":"Europe/Paris"},"graph":{"$ref":"#/components/schemas/SyncFlowGraph"},"notify":{"$ref":"#/components/schemas/SyncNotify"}}},"SyncFlow":{"type":"object","required":["id","name","enabled","graph","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"enabled":{"type":"boolean"},"schedule":{"type":["string","null"]},"timezone":{"type":"string"},"graph":{"$ref":"#/components/schemas/SyncFlowGraph"},"notify":{"$ref":"#/components/schemas/SyncNotify"},"nextRunAt":{"type":["string","null"],"format":"date-time"},"lastRun":{"oneOf":[{"$ref":"#/components/schemas/SyncFlowRun"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SyncNotify":{"type":"object","description":"E-mail sent when an import (or an execution of a chain) ends, with the workspace, the counts and the error. Uses the platform SMTP settings.","properties":{"on":{"type":"string","enum":["never","failure","always"],"default":"never","description":"failure = failed, timed out or stopped; always = every end."},"emails":{"type":"array","maxItems":20,"items":{"type":"string","format":"email"}}}},"SmtpSettings":{"type":"object","required":["configured","source","host","port","security","from"],"properties":{"configured":{"type":"boolean","description":"False while the platform still uses the deployment default (REMIZO_SMTP_URL)."},"source":{"type":"string","enum":["settings","environment"]},"host":{"type":"string"},"port":{"type":"integer"},"security":{"type":"string","enum":["none","starttls","tls"]},"username":{"type":["string","null"]},"hasPassword":{"type":"boolean"},"from":{"type":"string"},"replyTo":{"type":["string","null"]},"updatedAt":{"type":["string","null"],"format":"date-time"},"updatedBy":{"type":["string","null"]}}},"SmtpSettingsUpdate":{"type":"object","required":["host","port","security","from"],"properties":{"host":{"type":"string","minLength":1,"maxLength":255},"port":{"type":"integer","minimum":1,"maximum":65535},"security":{"type":"string","enum":["none","starttls","tls"]},"username":{"type":["string","null"],"maxLength":255},"password":{"type":["string","null"],"maxLength":1024,"writeOnly":true,"description":"Omitted = unchanged; null or empty = removed. Stored in OpenBao, never returned."},"from":{"type":"string","minLength":3,"maxLength":255,"description":"Sender, e.g. Remizo <no-reply@example.com>."},"replyTo":{"type":["string","null"],"maxLength":255}}},"SmtpTestResult":{"type":"object","required":["ok","ms"],"properties":{"ok":{"type":"boolean"},"ms":{"type":"integer"},"error":{"type":["string","null"]}}},"SyncFlowRun":{"type":"object","required":["id","flowId","status","startedAt","runs"],"properties":{"id":{"type":"string","format":"uuid"},"flowId":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/RunStatus"},"trigger":{"type":"string","enum":["schedule","manual"]},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"startedBy":{"type":["string","null"]},"runs":{"type":"array","description":"Latest run of each connector of the chain reached so far.","items":{"$ref":"#/components/schemas/SyncRun"}}}},"SyncReject":{"type":"object","required":["lineNo","reason"],"properties":{"id":{"type":"string","format":"uuid"},"lineNo":{"type":"integer"},"key":{"type":["string","null"]},"reason":{"type":"string"},"field":{"type":["string","null"]},"payload":{"type":"object","additionalProperties":true}}},"SyncMappingPreview":{"type":"object","required":["total","sourceFields","records","sampleMapped","sampleRejects"],"properties":{"total":{"type":"integer","description":"Records found in the sample."},"sourceFields":{"type":"array","items":{"type":"string"},"description":"Source field paths found (nested JSON as a.b), in first-seen order."},"records":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"First source records as parsed."},"sampleMapped":{"type":"array","items":{"type":"object","additionalProperties":true}},"sampleRejects":{"type":"array","items":{"$ref":"#/components/schemas/SyncReject"}},"mappingErrors":{"type":"array","items":{"type":"object","required":["field","message"],"properties":{"field":{"type":"string"},"message":{"type":"string"}}},"description":"Invalid CEL expressions (the preview skips them)."}}},"SyncTestResult":{"type":"object","properties":{"connectivity":{"type":"string","enum":["ok","failed","skipped"]},"connectivityDetail":{"type":"string"},"filesFound":{"type":"array","items":{"type":"string"}},"steps":{"type":"array","description":"Steps actually run, in order. A failed step stops the test: the following ones are absent.","items":{"$ref":"#/components/schemas/SyncTestStep"}},"sampleMapped":{"type":"array","items":{"type":"object","additionalProperties":true}},"sampleRejects":{"type":"array","items":{"$ref":"#/components/schemas/SyncReject"}}}},"SyncTestStep":{"type":"object","required":["key","status"],"properties":{"key":{"type":"string","enum":["connect","list","download","parse","fetch","map"]},"status":{"type":"string","enum":["ok","failed","skipped"]},"ms":{"type":"integer","description":"Duration of the step."},"detail":{"type":"string","description":"Error message, what was contacted (user@host:port, bucket/prefix, METHOD url, folder and pattern), or why the step was skipped (sample, no-file, push)."},"count":{"type":"integer","description":"Files found (list), records read (parse, fetch), records tested (map)."},"file":{"type":"string"},"bytes":{"type":"integer","description":"Bytes read from the file (download). Only the start of the file is read."},"size":{"type":"integer","description":"Size of the whole file."},"ok":{"type":"integer"},"ko":{"type":"integer"},"format":{"type":"string"}}},"ApiKeyScope":{"type":"string","enum":["transactions:write","transactions:read","points:write","points:read","coupons:write","coupons:read","customers:read","events:write","wsone"]},"ApiKeyCreate":{"type":"object","required":["name","scopes"],"properties":{"name":{"type":"string"},"env":{"type":"string","enum":["live","test"],"default":"live"},"scopes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ApiKeyScope"}},"storeCodes":{"type":"array","items":{"type":"string"},"description":"Empty = all stores."},"expiresAt":{"type":["string","null"],"format":"date-time"},"rateLimitPerSecond":{"type":["integer","null"]},"description":{"type":"string"}}},"ApiKey":{"type":"object","required":["keyId","name","env","scopes","status","createdAt"],"properties":{"keyId":{"type":"string","description":"Public id (8 chars) visible in the key prefix."},"name":{"type":"string"},"env":{"type":"string","enum":["live","test"]},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyScope"}},"storeCodes":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","revoked","expired"]},"prefix":{"type":"string","example":"rmz_live_7Hk2pQ9a_…"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"expiresAt":{"type":["string","null"],"format":"date-time"},"revokedAt":{"type":["string","null"],"format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"rateLimitPerSecond":{"type":["integer","null"]}}},"Member":{"type":"object","required":["id","sub","email","role","status"],"properties":{"id":{"type":"string","format":"uuid"},"sub":{"type":"string","description":"Keycloak user id."},"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"$ref":"#/components/schemas/WorkspaceRole"},"status":{"type":"string","enum":["invited","active","disabled"]},"storeCodes":{"type":"array","items":{"type":"string"},"description":"Optional store restriction (support role)."},"lastLoginAt":{"type":["string","null"],"format":"date-time"},"invitedBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"MemberInvite":{"type":"object","required":["email","role"],"properties":{"email":{"type":"string","format":"email"},"role":{"$ref":"#/components/schemas/WorkspaceRole"},"name":{"type":"string"},"storeCodes":{"type":"array","items":{"type":"string"}},"locale":{"type":"string"}}},"SsoConfig":{"type":"object","required":["provider","enabled"],"properties":{"provider":{"type":"string","enum":["entra","google","oidc","saml"]},"enabled":{"type":"boolean"},"alias":{"type":"string","description":"Keycloak IdP alias (`<slug>-<provider>`)."},"domains":{"type":"array","items":{"type":"string"},"description":"E-mail domains routed to this IdP (Organization domains)."},"clientId":{"type":"string"},"clientSecret":{"type":"string","writeOnly":true},"issuer":{"type":"string","format":"uri"},"tenantId":{"type":"string"},"defaultRole":{"$ref":"#/components/schemas/WorkspaceRole"},"autoProvision":{"type":"boolean","default":true,"description":"Create the member on first SSO login."},"redirectUri":{"type":"string","format":"uri","readOnly":true}}},"StaffUser":{"type":"object","required":["sub","email","roles","status"],"properties":{"sub":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"roles":{"type":"array","items":{"$ref":"#/components/schemas/StaffRole"}},"status":{"type":"string","enum":["active","disabled"]},"mfaEnrolled":{"type":"boolean"},"lastLoginAt":{"type":["string","null"],"format":"date-time"}}},"StaffUserCreate":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"name":{"type":"string"},"roles":{"type":"array","items":{"$ref":"#/components/schemas/StaffRole"}},"status":{"type":"string","enum":["active","disabled"]}}},"UserSearchResult":{"type":"object","properties":{"sub":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"workspaces":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"role":{"$ref":"#/components/schemas/WorkspaceRole"}}}}}},"FeatureFlag":{"type":"string","description":"Dotted flag names. `features.ai` hides AI UI and returns 404 on /ai endpoints when off.","enum":["features.ai","features.ab_tests","features.segments","features.article_level","features.coupon_render","features.webhooks","features.sandbox","features.sync_connectors","features.wsone_adapter","features.points_burn","features.gdpr_tools","features.bulk_import","features.custom_cel","features.pos_demo"]},"FeatureDefinition":{"type":"object","required":["flag","default","description"],"properties":{"flag":{"$ref":"#/components/schemas/FeatureFlag"},"default":{"type":"boolean"},"description":{"type":"string"},"stage":{"type":"string","enum":["ga","beta","preview","stub"]}}},"WorkspaceFeatures":{"type":"object","required":["effective"],"properties":{"effective":{"type":"object","additionalProperties":{"type":"boolean"}},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}},"sources":{"type":"object","additionalProperties":{"type":"string","enum":["code_default","platform_default","workspace_override"]}}}},"OfferFolder":{"type":"object","required":["id","name","parentId","path"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"parentId":{"type":["string","null"],"format":"uuid"},"path":{"type":"string","example":"/Saisonnier/Noël"},"offerCount":{"type":"integer"},"children":{"type":"array","items":{"$ref":"#/components/schemas/OfferFolder"}}}},"OfferFolderCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":80},"parentId":{"type":["string","null"],"format":"uuid"}}},"Tag":{"type":"object","required":["name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":40},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"offerCount":{"type":"integer"}}},"OfferSummary":{"type":"object","required":["id","code","name","status","triggerType","level","mode","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"code":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"folderId":{"type":["string","null"],"format":"uuid"},"tags":{"type":"array","items":{"type":"string"}},"status":{"$ref":"#/components/schemas/Offer/$defs/OfferStatus"},"hasDraft":{"type":"boolean"},"publishedVersionNo":{"type":["integer","null"]},"draftVersionNo":{"type":["integer","null"]},"triggerType":{"type":"string","enum":["transaction","event","coupon"]},"level":{"type":"integer"},"mode":{"type":"string","enum":["cumulative","best_offer","best_line"]},"priority":{"type":"integer"},"validFrom":{"type":"string","format":"date-time"},"validTo":{"type":["string","null"],"format":"date-time"},"summary":{"type":"string","description":"Auto-generated one-liner from actions."},"budget":{"type":["object","null"],"properties":{"usedPct":{"type":"number"},"usesLeft":{"type":["integer","null"]},"amountLeftCents":{"type":["integer","null"]}}},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"}}},"OfferCreate":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"folderId":{"type":["string","null"],"format":"uuid"},"tags":{"type":"array","items":{"type":"string"}},"templateId":{"type":"string","format":"uuid","description":"Start from a template."},"definition":{"$ref":"#/components/schemas/Offer","description":"Full initial definition (id/code/status/version are overwritten by the server)."}}},"OfferVersionSummary":{"type":"object","required":["versionNo","status","createdAt","createdBy"],"properties":{"versionNo":{"type":"integer"},"status":{"type":"string","enum":["draft","pending_approval","approved","rejected","published","superseded","unpublished"]},"checksum":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"submittedAt":{"type":["string","null"],"format":"date-time"},"submittedBy":{"type":["string","null"]},"approvedAt":{"type":["string","null"],"format":"date-time"},"approvedBy":{"type":["string","null"]},"publishedAt":{"type":["string","null"],"format":"date-time"},"publishedBy":{"type":["string","null"]},"unpublishedAt":{"type":["string","null"],"format":"date-time"},"comment":{"type":["string","null"]},"rolledBackFrom":{"type":["integer","null"]}}},"OfferVersion":{"allOf":[{"$ref":"#/components/schemas/OfferVersionSummary"},{"type":"object","required":["definition"],"properties":{"definition":{"$ref":"#/components/schemas/Offer"},"etag":{"type":"string"},"validation":{"$ref":"#/components/schemas/ValidationReport"}}}]},"OfferDetail":{"type":"object","required":["offer","versions"],"properties":{"offer":{"$ref":"#/components/schemas/OfferSummary"},"draft":{"$ref":"#/components/schemas/OfferVersion"},"published":{"$ref":"#/components/schemas/OfferVersion"},"versions":{"type":"array","items":{"$ref":"#/components/schemas/OfferVersionSummary"}},"stats":{"$ref":"#/components/schemas/OfferStats"}}},"OfferDiff":{"type":"object","required":["from","to","changes"],"properties":{"from":{"type":"integer"},"to":{"type":"integer"},"changes":{"type":"array","items":{"type":"object","required":["op","path"],"properties":{"op":{"type":"string","enum":["add","remove","replace"]},"path":{"type":"string","description":"JSON pointer in the definition."},"before":{},"after":{},"label":{"type":"string","description":"Human label (fr/en) for the studio."}}}}}},"ValidationReport":{"type":"object","required":["valid","issues"],"properties":{"valid":{"type":"boolean"},"issues":{"type":"array","items":{"type":"object","required":["severity","code","message"],"properties":{"severity":{"$ref":"#/components/schemas/Severity"},"code":{"type":"string","enum":["schema","unknown_reference","cel_error","target_not_article_criterion","limits_incoherent","validity_past","overlapping_exclusive","duplicate_node_id","counter_type_unknown","template_unknown","segment_unknown","product_list_unknown","ab_test_unknown","feature_disabled"]},"path":{"type":"string"},"message":{"type":"string"}}}}}},"PublishResult":{"type":"object","required":["offer","version","engines"],"properties":{"offer":{"$ref":"#/components/schemas/OfferSummary"},"version":{"$ref":"#/components/schemas/OfferVersionSummary"},"engines":{"type":"array","items":{"type":"object","properties":{"instance":{"type":"string"},"reloaded":{"type":"boolean"},"durationMs":{"type":"number"},"error":{"type":"string"}}}},"messageId":{"type":"string","description":"RabbitMQ message id."}}},"OfferStats":{"type":"object","properties":{"offerId":{"type":"string","format":"uuid"},"code":{"type":"string"},"name":{"type":"string"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"evaluated":{"type":"integer"},"applied":{"type":"integer"},"rejected":{"type":"integer"},"rejectReasons":{"type":"object","additionalProperties":{"type":"integer"}},"discountCents":{"type":"integer"},"pointsEarned":{"type":"integer"},"couponsIssued":{"type":"integer"},"couponsRedeemed":{"type":"integer"},"ticketsWithOfferAvgCents":{"type":"integer"},"uniqueCustomers":{"type":"integer"},"budget":{"type":"object","properties":{"maxUses":{"type":["integer","null"]},"uses":{"type":"integer"},"maxAmountCents":{"type":["integer","null"]},"amountCents":{"type":"integer"}}}}},"OfferTemplate":{"type":"object","required":["name","category","definition"],"properties":{"id":{"type":"string","format":"uuid"},"scope":{"type":"string","enum":["platform","workspace"],"readOnly":true},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","enum":["welcome","discount","points","bundle","coupon","birthday","article","seasonal","custom"]},"icon":{"type":"string","description":"lucide icon name."},"definition":{"$ref":"#/components/schemas/Offer"},"variables":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"label":{"type":"string"},"type":{"type":"string","enum":["cents","bp","integer","string","date","storeGroup","counterType","productList","couponTemplate"]}}},"description":"Fields the wizard asks for."}}},"ProductListCreate":{"type":"object","required":["name"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"matchOn":{"type":"string","enum":["ean","ref","both"],"default":"both"},"kind":{"type":"string","enum":["static","rules"],"default":"static","description":"static = articles listed (file, API, sync); rules = articles of the catalogue that match `rules`, recomputed after each article import"},"rules":{"$ref":"#/components/schemas/ProductListRules"}}},"ProductListRules":{"type":"object","description":"Criteria are combined with AND, the values of one criterion with OR (case-insensitive). The engine also applies them to ticket lines whose article is not in the catalogue (rayon = department, category = family, attributes.subfamily, brand, unit price).","properties":{"departments":{"type":"array","items":{"type":"string"},"maxItems":500},"families":{"type":"array","items":{"type":"string"},"maxItems":500},"subfamilies":{"type":"array","items":{"type":"string"},"maxItems":500},"brands":{"type":"array","items":{"type":"string"},"maxItems":500},"priceMinCents":{"type":["integer","null"],"minimum":0},"priceMaxCents":{"type":["integer","null"],"minimum":0},"includeInactive":{"type":"boolean","default":false},"includeRefs":{"type":"array","items":{"type":"string"},"maxItems":500,"description":"Always in the list, even outside the rules or the catalogue."},"excludeRefs":{"type":"array","items":{"type":"string"},"maxItems":500,"description":"Never in the list."}}},"ProductListRulesPreview":{"type":"object","required":["count","fromCatalog","outsideCatalog","sample"],"properties":{"count":{"type":"integer"},"fromCatalog":{"type":"integer"},"outsideCatalog":{"type":"array","items":{"type":"string"},"description":"Included references unknown to the catalogue."},"sample":{"type":"array","items":{"type":"object","required":["ref","label"],"properties":{"ref":{"type":"string"},"ean":{"type":"string"},"label":{"type":"string"},"department":{"type":"string"},"family":{"type":"string"},"brand":{"type":"string"},"priceCents":{"type":"integer"}}}}}},"ProductList":{"allOf":[{"$ref":"#/components/schemas/ProductListCreate"},{"type":"object","required":["id","itemCount"],"properties":{"id":{"type":"string","format":"uuid"},"itemCount":{"type":"integer"},"usedByOffers":{"type":"integer"},"updatedAt":{"type":"string","format":"date-time"},"refreshedAt":{"type":["string","null"],"format":"date-time","description":"Last computation of a rules list."}}}]},"ProductListItem":{"type":"object","properties":{"ean":{"type":"string"},"ref":{"type":"string"},"label":{"type":"string"}},"anyOf":[{"required":["ean"]},{"required":["ref"]}]},"SimulateRequest":{"type":"object","required":["ticket"],"properties":{"ticket":{"$ref":"#/components/schemas/Ticket"},"drafts":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Offers whose draft is used instead of the published version."},"offerIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Restrict published offers."},"includePublished":{"type":"boolean","default":true},"at":{"type":"string","format":"date-time"},"customerOverride":{"type":"object","additionalProperties":true}}},"SampleTicket":{"type":"object","required":["name","ticket"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"ticket":{"$ref":"#/components/schemas/Ticket"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}},"CouponFormat":{"type":"string","enum":["ean13","qr","short"]},"CouponState":{"type":"string","enum":["available","reserved","used","expired","cancelled"]},"CampaignStatus":{"type":"string","enum":["draft","generating","ready","active","closed","cancelled","failed"]},"CouponDesign":{"type":"object","description":"Template design. `svg` with {{placeholders}} rendered by resvg (png) or wrapped in HTML for Gotenberg (pdf).","required":["kind"],"properties":{"kind":{"type":"string","enum":["svg","html"]},"source":{"type":"string","description":"SVG or HTML source with placeholders: {{code}}, {{barcode}}, {{qr}}, {{faceValue}}, {{validTo}}, {{customer.firstName}}, {{offer.name}}, {{store.name}}"},"width":{"type":"integer","description":"px for png; mm for pdf."},"height":{"type":"integer"},"pageFormat":{"type":"string","enum":["A4","A5","A6","ticket80mm","custom"]},"fonts":{"type":"array","items":{"type":"string"}},"assets":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"objectKey":{"type":"string"}}}}}},"CouponTemplateCreate":{"type":"object","required":["code","name","format"],"properties":{"code":{"type":"string","pattern":"^[A-Z0-9][A-Z0-9_-]{1,31}$"},"name":{"type":"string"},"description":{"type":"string"},"format":{"$ref":"#/components/schemas/CouponFormat"},"codePattern":{"type":"string","description":"ean13: 12-digit prefix pattern with # for random digits (check digit computed); short: e.g. \"WELC-####-####\"; qr: payload template."},"defaultValidity":{"type":"object","properties":{"mode":{"type":"string","enum":["fixed","rolling"]},"days":{"type":"integer"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}},"reusable":{"type":"boolean","default":false},"maxUses":{"type":["integer","null"]},"render":{"type":"string","enum":["none","png","pdf"],"default":"none"},"design":{"$ref":"#/components/schemas/CouponDesign"},"printText":{"type":"string","description":"Text printed under the barcode on the POS ticket."},"linkedOfferId":{"type":["string","null"],"format":"uuid"}}},"CouponTemplate":{"allOf":[{"$ref":"#/components/schemas/CouponTemplateCreate"},{"type":"object","required":["id","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"issuedCount":{"type":"integer"},"usedByOffers":{"type":"integer"},"previewUrl":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"CouponCampaignCreate":{"type":"object","required":["name","templateId"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"templateId":{"type":"string","format":"uuid"},"linkedOfferId":{"type":["string","null"],"format":"uuid"},"validity":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}},"faceValue":{"$ref":"#/components/schemas/EvaluationResult/$defs/CouponIssued/properties/faceValue"}}},"CouponCampaign":{"allOf":[{"$ref":"#/components/schemas/CouponCampaignCreate"},{"type":"object","required":["id","status","counts"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/CampaignStatus"},"counts":{"type":"object","properties":{"requested":{"type":"integer"},"generated":{"type":"integer"},"imported":{"type":"integer"},"available":{"type":"integer"},"used":{"type":"integer"},"expired":{"type":"integer"},"cancelled":{"type":"integer"}}},"jobId":{"type":["string","null"],"format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"}}}]},"Coupon":{"type":"object","required":["id","code","format","state","templateId","validFrom","validTo","issuedAt"],"properties":{"id":{"type":"string","format":"uuid"},"code":{"type":"string"},"format":{"$ref":"#/components/schemas/CouponFormat"},"state":{"$ref":"#/components/schemas/CouponState"},"templateId":{"type":"string","format":"uuid"},"templateName":{"type":"string"},"campaignId":{"type":["string","null"],"format":"uuid"},"offerId":{"type":["string","null"],"format":"uuid","description":"Issuing offer."},"linkedOfferId":{"type":["string","null"],"format":"uuid"},"customerId":{"type":["string","null"],"format":"uuid"},"issuedAt":{"type":"string","format":"date-time"},"issuedTransactionId":{"type":["string","null"],"format":"uuid"},"validFrom":{"type":"string","format":"date-time"},"validTo":{"type":"string","format":"date-time"},"reservedAt":{"type":["string","null"],"format":"date-time"},"usedAt":{"type":["string","null"],"format":"date-time"},"usedTransactionId":{"type":["string","null"],"format":"uuid"},"cancelledAt":{"type":["string","null"],"format":"date-time"},"reusable":{"type":"boolean"},"maxUses":{"type":["integer","null"]},"uses":{"type":"integer"},"render":{"$ref":"#/components/schemas/EvaluationResult/$defs/CouponIssued/properties/render"},"faceValue":{"$ref":"#/components/schemas/EvaluationResult/$defs/CouponIssued/properties/faceValue"},"events":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","format":"date-time"},"from":{"type":["string","null"]},"to":{"type":"string"},"actor":{"type":"string"},"transactionId":{"type":["string","null"]},"reason":{"type":["string","null"]}}}}}},"CustomerStatus":{"type":"string","enum":["active","blocked","prospect","merged","anonymized"]},"CardStatus":{"type":"string","enum":["active","blocked","lost","replaced","expired"]},"CustomerSummary":{"type":"object","required":["id","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"externalRef":{"type":["string","null"]},"firstName":{"type":["string","null"]},"lastName":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/CustomerStatus"},"loyaltyStatus":{"type":["string","null"]},"primaryCard":{"type":["string","null"]},"storeCode":{"type":["string","null"]},"balances":{"type":"array","items":{"type":"object","properties":{"counterTypeCode":{"type":"string"},"available":{"type":"integer"}}}},"lastTransactionAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"CustomerCreate":{"type":"object","properties":{"externalRef":{"type":"string"},"accountId":{"type":["string","null"],"format":"uuid"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"birthDate":{"type":["string","null"],"format":"date"},"gender":{"type":["string","null"],"enum":["f","m","x",null]},"address":{"type":"object","properties":{"line1":{"type":"string"},"line2":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"}}},"storeCode":{"type":["string","null"]},"loyaltyStatus":{"type":["string","null"]},"optins":{"type":"object","properties":{"marketingEmail":{"type":"boolean"},"marketingSms":{"type":"boolean"},"profiling":{"type":"boolean"}}},"tags":{"type":"array","items":{"type":"string"}},"attributes":{"type":"object","additionalProperties":true},"cardNumber":{"type":"string","description":"Create the first card at the same time."},"signupAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["active","blocked","prospect"]}}},"Customer":{"allOf":[{"$ref":"#/components/schemas/CustomerCreate"},{"type":"object","required":["id","status","cards","identifiers","balances","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/CustomerStatus"},"mergedInto":{"type":["string","null"],"format":"uuid"},"anonymizedAt":{"type":["string","null"],"format":"date-time"},"cards":{"type":"array","items":{"$ref":"#/components/schemas/Card"}},"identifiers":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}},"balances":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/Balance"}},"segments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}}},"stats":{"type":"object","properties":{"transactions":{"type":"integer"},"totalSpentCents":{"type":"integer"},"totalDiscountCents":{"type":"integer"},"lastTransactionAt":{"type":["string","null"],"format":"date-time"},"firstTransactionAt":{"type":["string","null"],"format":"date-time"}}},"source":{"type":"string","enum":["manual","pos_auto","import","sync","api"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"CardCreate":{"type":"object","required":["number"],"properties":{"number":{"type":"string"},"type":{"type":"string","default":"physical","enum":["physical","virtual","app"]},"expiresAt":{"type":["string","null"],"format":"date-time"}}},"Card":{"allOf":[{"$ref":"#/components/schemas/CardCreate"},{"type":"object","required":["id","status","issuedAt"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/CardStatus"},"autoCreated":{"type":"boolean"},"issuedAt":{"type":"string","format":"date-time"},"blockedAt":{"type":["string","null"],"format":"date-time"},"replacedBy":{"type":["string","null"],"format":"uuid"}}}]},"MovementKind":{"type":"string","enum":["earn","burn","reserve","release","adjust","expire","reset","transfer_in","transfer_out","reversal"]},"Movement":{"type":"object","required":["id","customerId","counterTypeCode","kind","amount","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"counterTypeCode":{"type":"string"},"kind":{"$ref":"#/components/schemas/MovementKind"},"amount":{"type":"integer","description":"Signed."},"balanceAfter":{"type":["integer","null"]},"state":{"type":"string","enum":["posted","pending_activation","reserved","confirmed","cancelled","expired"]},"reasonCode":{"type":["string","null"]},"reasonText":{"type":["string","null"]},"offerId":{"type":["string","null"],"format":"uuid"},"offerCode":{"type":["string","null"]},"transactionId":{"type":["string","null"],"format":"uuid"},"ticketKey":{"type":["string","null"]},"reservationId":{"type":["string","null"],"format":"uuid"},"reversesMovementId":{"type":["string","null"],"format":"uuid"},"actor":{"type":"string","description":"engine | cron | user:<sub> | apikey:<keyId>"},"storeCode":{"type":["string","null"]},"activatesAt":{"type":["string","null"],"format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"AdjustmentCreate":{"type":"object","required":["counterTypeCode","amount","reasonCode","reasonText"],"properties":{"counterTypeCode":{"type":"string"},"amount":{"type":"integer","description":"Signed; non-zero."},"reasonCode":{"type":"string","enum":["goodwill","complaint","correction","migration","fraud","other"]},"reasonText":{"type":"string","minLength":3,"maxLength":500},"expiresAt":{"type":["string","null"],"format":"date-time"},"storeCode":{"type":"string"}}},"TransactionState":{"type":"string","enum":["pending","committed","cancelled","refunded","partially_refunded","expired"]},"TransactionSummary":{"type":"object","required":["transactionId","ticketKey","state","kind","storeCode","timestamp","grossCents","discountCents"],"properties":{"transactionId":{"type":"string","format":"uuid"},"ticketKey":{"type":"string"},"kind":{"type":"string","enum":["sale","refund"]},"state":{"$ref":"#/components/schemas/TransactionState"},"storeCode":{"type":"string"},"posId":{"type":"string"},"ticketNumber":{"type":"string"},"channel":{"type":"string"},"timestamp":{"type":"string","format":"date-time"},"customerId":{"type":["string","null"],"format":"uuid"},"grossCents":{"type":"integer"},"discountCents":{"type":"integer"},"pointsEarned":{"type":"integer"},"pointsBurnt":{"type":"integer"},"couponsIssued":{"type":"integer"},"offersApplied":{"type":"array","items":{"type":"object","properties":{"offerId":{"type":"string"},"code":{"type":"string"},"valueCents":{"type":"integer"}}}},"adapter":{"type":"string","enum":["native","wsone"]}}},"ImportJob":{"type":"object","required":["id","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["customers","coupons","product_list","stores"]},"status":{"$ref":"#/components/schemas/RunStatus"},"dryRun":{"type":"boolean"},"filename":{"type":"string"},"counts":{"$ref":"#/components/schemas/ImportSummary"},"createdBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]}}},"CustomerEventCreate":{"type":"object","required":["type","occurredAt"],"properties":{"type":{"type":"string","enum":["signup","card_created","birthday","anniversary_signup","status_changed","segment_entered","segment_left","counter_threshold","custom"]},"customType":{"type":"string"},"occurredAt":{"type":"string","format":"date-time"},"storeCode":{"type":"string"},"payload":{"type":"object","additionalProperties":true}}},"CustomerEvent":{"allOf":[{"$ref":"#/components/schemas/CustomerEventCreate"},{"type":"object","required":["id","customerId","source"],"properties":{"id":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"source":{"type":"string","enum":["api","sync","cron","engine","studio"]},"processedAt":{"type":["string","null"],"format":"date-time"},"offersApplied":{"type":"array","items":{"type":"string","format":"uuid"}}}}]},"CounterType":{"type":"object","required":["code","name","kind","unit"],"properties":{"code":{"type":"string","pattern":"^[A-Z0-9][A-Z0-9_]{1,31}$","example":"POINTS"},"name":{"type":"string"},"kind":{"type":"string","enum":["points","cashback","visits","technical"]},"unit":{"type":"string","example":"pts","description":"cashback: unit = currency cents"},"decimals":{"type":"integer","minimum":0,"maximum":2,"default":0,"description":"Display only; storage is integer units."},"allowNegative":{"type":"boolean","default":false},"cap":{"type":["integer","null"],"description":"Maximum balance."},"expiry":{"type":"object","properties":{"mode":{"type":"string","enum":["never","days","end_of_month","end_of_year","fixed_day_of_year"]},"days":{"type":"integer"},"dayOfYear":{"type":"string","pattern":"^\\d{2}-\\d{2}$"},"warningDays":{"type":"integer"}}},"periodicReset":{"type":"object","properties":{"period":{"type":"string","enum":["none","day","week","month","quarter","year"]},"cap":{"type":["integer","null"]}}},"activationDelayDays":{"type":"integer","default":0},"valueCentsPerUnit":{"type":["integer","null"],"description":"Monetary value for burn (e.g. 1 point = 1 cent)."},"burnable":{"type":"boolean","default":false},"visibleToCustomer":{"type":"boolean","default":true},"wsoneAccountName":{"type":["string","null"],"description":"Name exposed in WsOne AccountResponse (default code)."},"archivedAt":{"type":["string","null"],"format":"date-time"}}},"LoyaltyStatus":{"type":"object","required":["code","name","rank"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"rank":{"type":"integer"},"color":{"type":"string"},"rule":{"type":["object","null"],"properties":{"counterTypeCode":{"type":"string"},"minBalance":{"type":"integer"},"period":{"type":"string","enum":["ever","rolling_12m","calendar_year"]}},"description":"Optional automatic tiering rule evaluated by cron `customers.tiers` (part of stats.rollup_daily)."}}},"SegmentDefinition":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["rules","sql","static"]},"rules":{"type":"object","description":"AND/OR tree of customer-level rules.","properties":{"op":{"type":"string","enum":["and","or"]},"children":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","description":"Customer field, balance.<code>, stats.<metric> (spent_90d, tickets_90d, last_visit_days, avg_basket_cents), tag, store, status, optin.<name>"},"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","between","in","not_in","contains","exists","not_exists","before","after","within_days"]},"value":{}}}}}},"sql":{"type":"string","description":"Read-only SELECT returning customer_id (validated: single statement, allowlisted tables)."},"customerIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},"SegmentCreate":{"type":"object","required":["name","definition"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"definition":{"$ref":"#/components/schemas/SegmentDefinition"},"schedule":{"type":["string","null"]},"timezone":{"type":"string"},"emitEvents":{"type":"boolean","default":false,"description":"Emit segment_entered / segment_left customer events on recompute."}}},"Segment":{"allOf":[{"$ref":"#/components/schemas/SegmentCreate"},{"type":"object","required":["id","size","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"size":{"type":"integer"},"lastComputedAt":{"type":["string","null"],"format":"date-time"},"nextComputeAt":{"type":["string","null"],"format":"date-time"},"usedByOffers":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"SegmentRun":{"type":"object","required":["id","segmentId","status","startedAt"],"properties":{"id":{"type":"string","format":"uuid"},"segmentId":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/RunStatus"},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"size":{"type":"integer"},"entered":{"type":"integer"},"left":{"type":"integer"},"error":{"type":["string","null"]}}},"AbTestStatus":{"type":"string","enum":["draft","running","stopped","completed"]},"AbTestCreate":{"type":"object","required":["name","assignment","variants"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"hypothesis":{"type":"string"},"assignment":{"type":"string","enum":["customer","ticket","store"],"description":"Unit of randomisation; sticky per customer when customer."},"variants":{"type":"array","minItems":2,"items":{"type":"object","required":["key","weightBp"],"properties":{"key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,20}$"},"name":{"type":"string"},"weightBp":{"type":"integer","minimum":0,"maximum":10000},"offerIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Offers using ab_variant criterion on this variant (informational)."}}}},"segmentId":{"type":["string","null"],"format":"uuid","description":"Restrict the population."},"primaryMetric":{"type":"string","enum":["conversion","basket_cents","discount_cents","points","coupon_redemption","margin_proxy"]},"startAt":{"type":["string","null"],"format":"date-time"},"endAt":{"type":["string","null"],"format":"date-time"}}},"AbTest":{"allOf":[{"$ref":"#/components/schemas/AbTestCreate"},{"type":"object","required":["id","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/AbTestStatus"},"startedAt":{"type":["string","null"],"format":"date-time"},"stoppedAt":{"type":["string","null"],"format":"date-time"},"winnerVariant":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}}}]},"AbTestResults":{"type":"object","required":["testId","variants"],"properties":{"testId":{"type":"string","format":"uuid"},"computedAt":{"type":"string","format":"date-time"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"exposures":{"type":"integer"},"tickets":{"type":"integer"},"conversions":{"type":"integer"},"conversionRate":{"type":"number"},"avgBasketCents":{"type":"integer"},"discountCents":{"type":"integer"},"pointsEarned":{"type":"integer"},"upliftPct":{"type":["number","null"],"description":"vs control (first variant)."},"confidence":{"type":["number","null"],"description":"0-1, two-proportion z-test on primaryMetric."}}}},"roi":{"type":"object","properties":{"incrementalRevenueCents":{"type":"integer"},"costCents":{"type":"integer"},"ratio":{"type":"number"}}}}},"StatsMetric":{"type":"string","enum":["tickets","identified_tickets","gross_cents","discount_cents","points_earned","points_burnt","coupons_issued","coupons_redeemed","offers_applied","new_customers","active_customers","avg_basket_cents"]},"StatsOverview":{"type":"object","required":["from","to","kpis"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"kpis":{"type":"object","additionalProperties":{"type":"object","properties":{"value":{"type":"number"},"previous":{"type":["number","null"]},"deltaPct":{"type":["number","null"]}}},"description":"Keys = StatsMetric values."},"topOffers":{"type":"array","items":{"$ref":"#/components/schemas/OfferStats"}},"topStores":{"type":"array","items":{"$ref":"#/components/schemas/StoreStats"}}}},"Timeseries":{"type":"object","required":["granularity","series"],"properties":{"granularity":{"type":"string"},"series":{"type":"array","items":{"type":"object","properties":{"metric":{"$ref":"#/components/schemas/StatsMetric"},"points":{"type":"array","items":{"type":"object","properties":{"t":{"type":"string","format":"date-time"},"v":{"type":"number"}}}}}}}}},"StoreStats":{"type":"object","properties":{"storeCode":{"type":"string"},"storeName":{"type":"string"},"tickets":{"type":"integer"},"identifiedTickets":{"type":"integer"},"grossCents":{"type":"integer"},"discountCents":{"type":"integer"},"pointsEarned":{"type":"integer"},"couponsIssued":{"type":"integer"},"offersApplied":{"type":"integer"}}},"CounterStats":{"type":"object","properties":{"counterTypeCode":{"type":"string"},"earned":{"type":"integer"},"burnt":{"type":"integer"},"expired":{"type":"integer"},"adjusted":{"type":"integer"},"outstanding":{"type":"integer"},"outstandingValueCents":{"type":["integer","null"]},"expiringNext30d":{"type":"integer"},"customersWithBalance":{"type":"integer"}}},"CouponStats":{"type":"object","properties":{"templateId":{"type":"string","format":"uuid"},"templateName":{"type":"string"},"campaignId":{"type":["string","null"]},"issued":{"type":"integer"},"redeemed":{"type":"integer"},"expired":{"type":"integer"},"cancelled":{"type":"integer"},"redemptionRate":{"type":"number"},"medianDelayHours":{"type":["number","null"]}}},"CustomerStats":{"type":"object","properties":{"activeCustomers":{"type":"integer"},"newCustomers":{"type":"integer"},"newCards":{"type":"integer"},"autoCreatedCards":{"type":"integer"},"identifiedBasketCents":{"type":"integer"},"anonymousBasketCents":{"type":"integer"},"byLoyaltyStatus":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string"},"customers":{"type":"integer"},"tickets":{"type":"integer"},"grossCents":{"type":"integer"}}}}}},"ExportJob":{"type":"object","required":["id","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"report":{"type":"string"},"format":{"type":"string"},"status":{"$ref":"#/components/schemas/RunStatus"},"rows":{"type":["integer","null"]},"link":{"$ref":"#/components/schemas/SignedLink"},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]}}},"AuditEntry":{"type":"object","required":["id","at","actor","action","targetType"],"properties":{"id":{"type":"string","format":"uuid"},"at":{"type":"string","format":"date-time"},"workspace":{"type":["string","null"]},"actor":{"type":"object","properties":{"type":{"type":"string","enum":["user","staff","apikey","system","cron"]},"id":{"type":"string"},"email":{"type":["string","null"]},"ip":{"type":["string","null"]}}},"action":{"type":"string","description":"<resource>.<verb>, e.g. offer.publish, customer.adjust, apikey.revoke, backup.restore"},"targetType":{"type":"string"},"targetId":{"type":["string","null"]},"targetLabel":{"type":["string","null"]},"before":{"type":["object","null"],"additionalProperties":true},"after":{"type":["object","null"],"additionalProperties":true},"reason":{"type":["string","null"]},"requestId":{"type":["string","null"]}}},"WebhookEventType":{"type":"string","enum":["ping","transaction.committed","transaction.cancelled","transaction.refunded","offer.applied","offer.published","offer.suspended","offer.budget_exhausted","coupon.issued","coupon.rendered","coupon.redeemed","coupon.expired","coupon.cancelled","points.earned","points.burnt","points.expired","points.adjusted","customer.created","customer.merged","customer.anonymized","customer.event","segment.computed","sync.run_finished","backup.finished","alert.raised"]},"DeliveryStatus":{"type":"string","enum":["pending","delivered","failed","dead"]},"WebhookCreate":{"type":"object","required":["name","url","events"],"properties":{"name":{"type":"string"},"url":{"type":"string","format":"uri","description":"https only."},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEventType"}},"enabled":{"type":"boolean","default":true},"headers":{"type":"object","additionalProperties":{"type":"string"}},"filters":{"type":"object","properties":{"storeCodes":{"type":"array","items":{"type":"string"}},"offerIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},"retry":{"type":"object","properties":{"maxAttempts":{"type":"integer","default":8},"backoff":{"type":"string","enum":["exponential"],"default":"exponential"}}},"description":{"type":"string"}}},"Webhook":{"allOf":[{"$ref":"#/components/schemas/WebhookCreate"},{"type":"object","required":["id","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"secretHint":{"type":"string","description":"Last 4 chars."},"stats24h":{"type":"object","properties":{"delivered":{"type":"integer"},"failed":{"type":"integer"},"avgLatencyMs":{"type":"number"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"WebhookDelivery":{"type":"object","required":["id","webhookId","eventType","status","attempts","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"webhookId":{"type":"string","format":"uuid"},"eventId":{"type":"string","format":"uuid"},"eventType":{"$ref":"#/components/schemas/WebhookEventType"},"status":{"$ref":"#/components/schemas/DeliveryStatus"},"attempts":{"type":"integer"},"lastAttemptAt":{"type":["string","null"],"format":"date-time"},"nextAttemptAt":{"type":["string","null"],"format":"date-time"},"responseStatus":{"type":["integer","null"]},"responseBody":{"type":["string","null"],"description":"First 1 KiB."},"latencyMs":{"type":["number","null"]},"payload":{"type":"object","additionalProperties":true},"createdAt":{"type":"string","format":"date-time"}}},"AdapterKind":{"type":"string","enum":["native","wsone"]},"AdapterConfig":{"type":"object","required":["kind","enabled"],"properties":{"kind":{"$ref":"#/components/schemas/AdapterKind"},"enabled":{"type":"boolean"},"apiKeyHeader":{"type":"string","default":"X-Api-Key","description":"Header carrying the API key for this adapter (WsOne clients may use another)."},"wsone":{"type":"object","description":"WsOne specific mapping.","properties":{"entityToStore":{"type":"string","enum":["as_is","lookup_attribute"],"default":"as_is","description":"lookup_attribute = match stores.attributes[entityAttribute]"},"entityAttribute":{"type":"string"},"identifierTypes":{"type":"object","additionalProperties":{"type":"string"},"default":{"1":"card","10":"web_account","30":"wallet","99":"prospect"}},"universHierarchy":{"type":"string","enum":["univers_only","univers_rayon_category_from_lists"],"default":"univers_only","description":"How universReference is mapped onto univers/rayon/category."},"amountScale":{"type":"integer","default":2,"description":"Decimals in WsOne amounts."},"dateTimezone":{"type":"string","description":"Timezone of transactionDate (default store tz)."},"offerNumberIs":{"type":"string","enum":["coupon_code","offer_code","auto"],"default":"auto"},"responseTypes":{"type":"object","description":"Override numeric `type` codes per Remizo action family.","additionalProperties":{"type":"integer"}},"includeRejectedOffers":{"type":"boolean","default":false}}},"native":{"type":"object","properties":{"mapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional CEL mapping src -> ticket for custom POS payloads (ticket field -> expression)."},"strictSchema":{"type":"boolean","default":true}}},"updatedAt":{"type":"string","format":"date-time"}}},"Alert":{"type":"object","required":["id","kind","severity","state","raisedAt","title"],"properties":{"id":{"type":"string","format":"uuid"},"workspace":{"type":["string","null"]},"kind":{"type":"string","enum":["backup_failed","backup_too_old","backup_storage_low","restore_test_failed","cron_failed","cron_stuck","engine_down","engine_degraded","db_replica_lag","db_disk_low","db_failover","sync_failed","sync_reject_rate","webhook_dead","offer_budget_exhausted","queue_backlog","certificate_expiring"]},"severity":{"$ref":"#/components/schemas/Severity"},"state":{"type":"string","enum":["open","acknowledged","resolved"]},"title":{"type":"string"},"detail":{"type":"string"},"raisedAt":{"type":"string","format":"date-time"},"acknowledgedAt":{"type":["string","null"],"format":"date-time"},"acknowledgedBy":{"type":["string","null"]},"resolvedAt":{"type":["string","null"],"format":"date-time"},"link":{"type":["string","null"],"description":"Console deep link."}}},"AlertChannel":{"type":"object","required":["kind","enabled"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["teams","email","webhook"]},"name":{"type":"string"},"enabled":{"type":"boolean"},"workspace":{"type":["string","null"],"description":"null = platform-wide."},"target":{"type":"string","writeOnly":true,"description":"Teams webhook URL / e-mail list / URL (stored in OpenBao)."},"minSeverity":{"$ref":"#/components/schemas/Severity"},"kinds":{"type":"array","items":{"type":"string"}}}},"Offer":{"title":"Offer","description":"Remizo offer definition: trigger -> condition tree (AND/OR/NOT groups of typed criteria) -> actions, plus stacking, limits and validity. This exact document is what the studio edits, what offer_versions.definition stores, and what the engine compiles in memory once published. Amounts are integer cents; percentages are basis points (bp, 10000 = 100%).","type":"object","additionalProperties":false,"required":["id","code","name","status","version","trigger","criteria","actions","stacking","validity"],"properties":{"schemaVersion":{"type":"string","const":"1"},"id":{"type":"string","format":"uuid","description":"Stable offer id across versions."},"code":{"type":"string","pattern":"^[A-Z0-9][A-Z0-9_-]{1,31}$","description":"Human code, unique per workspace, immutable (used in POS messages and WsOne offerNumber)."},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":2000},"folderId":{"type":["string","null"],"format":"uuid"},"tags":{"type":"array","uniqueItems":true,"items":{"type":"string","maxLength":40}},"status":{"$ref":"#/components/schemas/Offer/$defs/OfferStatus"},"version":{"type":"integer","minimum":1,"description":"Version number of this definition (offer_versions.version_no)."},"publishedAt":{"type":["string","null"],"format":"date-time"},"trigger":{"$ref":"#/components/schemas/Offer/$defs/Trigger"},"criteria":{"$ref":"#/components/schemas/Offer/$defs/CriterionNode","description":"Root of the condition tree. An empty AND group means \"always\"."},"actions":{"type":"array","minItems":1,"maxItems":20,"items":{"$ref":"#/components/schemas/Offer/$defs/Action"},"description":"Executed in order when the criteria hold. Discount actions of the same offer are summed."},"stacking":{"$ref":"#/components/schemas/Offer/$defs/Stacking"},"limits":{"$ref":"#/components/schemas/Offer/$defs/Limits"},"validity":{"$ref":"#/components/schemas/Offer/$defs/Validity"},"coupon":{"$ref":"#/components/schemas/Offer/$defs/CouponRenderDefaults"},"ui":{"type":"object","description":"Studio-only data (React Flow node positions, notes). Ignored by the engine.","additionalProperties":true},"meta":{"type":"object","additionalProperties":false,"properties":{"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"updatedBy":{"type":"string"},"templateId":{"type":["string","null"],"format":"uuid","description":"Ready-made template this offer was created from."}}}},"$defs":{"Cents":{"type":"integer","minimum":0,"maximum":9223372036854776000},"Bp":{"type":"integer","minimum":0,"maximum":1000000,"description":"Basis points: 10000 = 100%. Multipliers may exceed 10000."},"I18nText":{"type":"object","description":"Map of BCP-47 language -> text. The workspace default locale key is required.","minProperties":1,"propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"},"additionalProperties":{"type":"string","maxLength":500}},"Weekday":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]},"Period":{"type":"string","enum":["ever","day","week","month","quarter","year","rolling_days","validity"],"description":"Calendar periods are computed in the offer/workspace timezone. rolling_days requires periodDays. validity = the offer validity window."},"StringOp":{"type":"string","enum":["eq","neq","in","not_in","contains","starts_with","regex","exists","not_exists"]},"NumberOp":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","between","exists","not_exists"]},"Rounding":{"type":"string","enum":["down","half_up","up"],"default":"half_up"},"Expiry":{"type":"object","additionalProperties":false,"required":["mode"],"description":"Expiry rule for credited counter units. none = counter type default.","properties":{"mode":{"type":"string","enum":["counter_default","never","days","end_of_month","end_of_year","fixed_date"]},"days":{"type":"integer","minimum":1},"date":{"type":"string","format":"date"}}},"OfferStatus":{"type":"string","enum":["draft","pending_approval","approved","published","suspended","expired","archived"],"description":"draft -> pending_approval -> approved -> published; published <-> suspended (emergency stop); published -> expired (validity.to passed); any -> archived. Only published versions are loaded by the engine."},"Trigger":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","enum":["transaction","event","coupon"],"description":"transaction: evaluated on every ticket. event: evaluated when a customer event occurs (no ticket). coupon: evaluated on tickets only when one of the trigger coupons is presented; the coupon is redeemed when the offer applies."},"event":{"type":"object","additionalProperties":false,"required":["types"],"properties":{"types":{"type":"array","minItems":1,"uniqueItems":true,"items":{"type":"string","enum":["signup","card_created","birthday","anniversary_signup","status_changed","segment_entered","segment_left","counter_threshold","custom"]}},"customTypes":{"type":"array","items":{"type":"string","maxLength":64}},"counterThreshold":{"type":"object","additionalProperties":false,"required":["counterTypeCode","threshold"],"properties":{"counterTypeCode":{"type":"string"},"threshold":{"type":"integer"},"direction":{"type":"string","enum":["crosses_up","crosses_down"],"default":"crosses_up"}}}}},"coupon":{"type":"object","additionalProperties":false,"properties":{"templateIds":{"type":"array","items":{"type":"string","format":"uuid"}},"campaignIds":{"type":"array","items":{"type":"string","format":"uuid"}},"codes":{"type":"array","items":{"type":"string"},"description":"Static promo codes (reusable, not stored as coupons)."},"redeem":{"type":"boolean","default":true,"description":"Mark the coupon used at commit."}}}},"allOf":[{"if":{"properties":{"type":{"const":"event"}}},"then":{"required":["event"]}},{"if":{"properties":{"type":{"const":"coupon"}}},"then":{"required":["coupon"]}}]},"CriterionNode":{"oneOf":[{"$ref":"#/components/schemas/Offer/$defs/Group"},{"$ref":"#/components/schemas/Offer/$defs/Criterion"}]},"Group":{"type":"object","additionalProperties":false,"required":["kind","op","children"],"properties":{"kind":{"const":"group"},"id":{"$ref":"#/components/schemas/Offer/$defs/NodeId"},"op":{"type":"string","enum":["and","or","not"],"description":"not takes exactly one child."},"label":{"type":"string","maxLength":120},"children":{"type":"array","items":{"$ref":"#/components/schemas/Offer/$defs/CriterionNode"}}},"if":{"properties":{"op":{"const":"not"}}},"then":{"properties":{"children":{"minItems":1,"maxItems":1}}}},"NodeId":{"type":"string","pattern":"^[a-zA-Z0-9_-]{1,40}$","description":"Unique within the offer. Needed when an action targets this criterion's matching lines."},"CriterionBase":{"type":"object","additionalProperties":false,"required":["kind","type","params"],"properties":{"kind":{"const":"criterion"},"id":{"$ref":"#/components/schemas/Offer/$defs/NodeId"},"type":{"$ref":"#/components/schemas/Offer/$defs/CriterionType"},"label":{"type":"string","maxLength":120},"negate":{"type":"boolean","default":false},"params":{"type":"object"}}},"CriterionType":{"type":"string","enum":["ticket_amount","ticket_item_count","date_range","time_range","weekdays","store","channel","customer_identified","customer_attribute","loyalty_status","segment","customer_anniversary","first_purchase","coupon_presented","payment_method","counter_balance","usage_count","ab_variant","event","custom_cel","article_ean","article_ref","article_list","article_category","article_rayon","article_univers","article_brand","article_attribute","article_price","article_lot"]},"Criterion":{"allOf":[{"$ref":"#/components/schemas/Offer/$defs/CriterionBase"},{"oneOf":[{"$ref":"#/components/schemas/Offer/$defs/criteria/TicketAmount"},{"$ref":"#/components/schemas/Offer/$defs/criteria/TicketItemCount"},{"$ref":"#/components/schemas/Offer/$defs/criteria/DateRange"},{"$ref":"#/components/schemas/Offer/$defs/criteria/TimeRange"},{"$ref":"#/components/schemas/Offer/$defs/criteria/Weekdays"},{"$ref":"#/components/schemas/Offer/$defs/criteria/Store"},{"$ref":"#/components/schemas/Offer/$defs/criteria/Channel"},{"$ref":"#/components/schemas/Offer/$defs/criteria/CustomerIdentified"},{"$ref":"#/components/schemas/Offer/$defs/criteria/CustomerAttribute"},{"$ref":"#/components/schemas/Offer/$defs/criteria/LoyaltyStatus"},{"$ref":"#/components/schemas/Offer/$defs/criteria/Segment"},{"$ref":"#/components/schemas/Offer/$defs/criteria/CustomerAnniversary"},{"$ref":"#/components/schemas/Offer/$defs/criteria/FirstPurchase"},{"$ref":"#/components/schemas/Offer/$defs/criteria/CouponPresented"},{"$ref":"#/components/schemas/Offer/$defs/criteria/PaymentMethod"},{"$ref":"#/components/schemas/Offer/$defs/criteria/CounterBalance"},{"$ref":"#/components/schemas/Offer/$defs/criteria/UsageCount"},{"$ref":"#/components/schemas/Offer/$defs/criteria/AbVariant"},{"$ref":"#/components/schemas/Offer/$defs/criteria/Event"},{"$ref":"#/components/schemas/Offer/$defs/criteria/CustomCel"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleEan"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleRef"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleList"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleCategory"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleRayon"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleUnivers"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleBrand"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleAttribute"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticlePrice"},{"$ref":"#/components/schemas/Offer/$defs/criteria/ArticleLot"}]}]},"ArticleMatch":{"type":"object","additionalProperties":false,"description":"Common thresholds for article criteria. The criterion holds when the matching lines (excluding flags no_promo for discounts) reach these thresholds. Matching lines become the criterion's eligible set, addressable by actions through target.criterionId.","properties":{"minQty":{"type":"number","minimum":0,"default":1,"description":"Minimum total quantity of matching lines."},"maxQty":{"type":"number","minimum":0},"minAmountCents":{"$ref":"#/components/schemas/Offer/$defs/Cents","description":"Minimum total of matching lineTotalCents."},"minLines":{"type":"integer","minimum":0}}},"criteria":{"TicketAmount":{"properties":{"type":{"const":"ticket_amount"},"params":{"type":"object","additionalProperties":false,"properties":{"base":{"type":"string","enum":["gross","pre_tax","eligible"],"default":"gross","description":"eligible = sum of lines without flags no_promo/gift_card/deposit/fee."},"minCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"maxCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"}},"anyOf":[{"required":["minCents"]},{"required":["maxCents"]}]}}},"TicketItemCount":{"properties":{"type":{"const":"ticket_item_count"},"params":{"type":"object","additionalProperties":false,"properties":{"countMode":{"type":"string","enum":["units","lines"],"default":"units"},"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0}},"anyOf":[{"required":["min"]},{"required":["max"]}]}}},"DateRange":{"properties":{"type":{"const":"date_range"},"params":{"type":"object","additionalProperties":false,"properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date","description":"Inclusive."},"recurringYearly":{"type":"boolean","default":false,"description":"Ignore the year (e.g. every 24/12-26/12)."}},"anyOf":[{"required":["from"]},{"required":["to"]}]}}},"TimeRange":{"properties":{"type":{"const":"time_range"},"params":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"from":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},"to":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"Exclusive. If to < from the range crosses midnight."},"timezone":{"type":"string","description":"IANA tz; default store timezone, then workspace timezone."}}}}},"Weekdays":{"properties":{"type":{"const":"weekdays"},"params":{"type":"object","additionalProperties":false,"required":["days"],"properties":{"days":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/Offer/$defs/Weekday"}}}}}},"Store":{"properties":{"type":{"const":"store"},"params":{"type":"object","additionalProperties":false,"properties":{"mode":{"type":"string","enum":["in","not_in"],"default":"in"},"storeCodes":{"type":"array","items":{"type":"string"}},"storeGroups":{"type":"array","items":{"type":"string"},"description":"Store group codes (store_groups table)."}},"anyOf":[{"required":["storeCodes"]},{"required":["storeGroups"]}]}}},"Channel":{"properties":{"type":{"const":"channel"},"params":{"type":"object","additionalProperties":false,"required":["channels"],"properties":{"channels":{"type":"array","minItems":1,"items":{"type":"string","enum":["pos","web","app","kiosk","callcenter","marketplace","other"]}}}}}},"CustomerIdentified":{"properties":{"type":{"const":"customer_identified"},"params":{"type":"object","additionalProperties":false,"properties":{"identifierTypes":{"type":"array","items":{"type":"string","enum":["card","web_account","wallet","prospect","customer","account","email","phone","external"]},"description":"Empty = any identifier."},"allowAutoCreated":{"type":"boolean","default":true,"description":"false = the card must have existed before this ticket."},"customerStatuses":{"type":"array","items":{"type":"string","enum":["active","blocked","prospect"]}}}}}},"CustomerAttribute":{"properties":{"type":{"const":"customer_attribute"},"params":{"type":"object","additionalProperties":false,"required":["attribute","op"],"properties":{"attribute":{"type":"string","description":"Built-in: gender, birthDate, signupAt, storeCode, optinMarketing, email, phone, city, postalCode, tags; or attributes.<key>."},"op":{"$ref":"#/components/schemas/Offer/$defs/StringOp"},"value":{"type":["string","number","boolean","null"]},"values":{"type":"array","items":{"type":["string","number","boolean"]}}}}}},"LoyaltyStatus":{"properties":{"type":{"const":"loyalty_status"},"params":{"type":"object","additionalProperties":false,"required":["statuses"],"properties":{"statuses":{"type":"array","minItems":1,"items":{"type":"string","maxLength":32},"description":"Loyalty status codes defined in workspace settings (e.g. bronze, silver, gold)."}}}}},"Segment":{"properties":{"type":{"const":"segment"},"params":{"type":"object","additionalProperties":false,"required":["segmentIds"],"properties":{"mode":{"type":"string","enum":["in_any","in_all","not_in"],"default":"in_any"},"segmentIds":{"type":"array","minItems":1,"items":{"type":"string","format":"uuid"}}}}}},"CustomerAnniversary":{"properties":{"type":{"const":"customer_anniversary"},"params":{"type":"object","additionalProperties":false,"required":["event"],"properties":{"event":{"type":"string","enum":["birthday","signup"]},"daysBefore":{"type":"integer","minimum":0,"default":0},"daysAfter":{"type":"integer","minimum":0,"default":0},"wholeMonth":{"type":"boolean","default":false}}}}},"FirstPurchase":{"properties":{"type":{"const":"first_purchase"},"params":{"type":"object","additionalProperties":false,"properties":{"scope":{"type":"string","enum":["ever","store","channel"],"default":"ever"}}}}},"CouponPresented":{"properties":{"type":{"const":"coupon_presented"},"params":{"type":"object","additionalProperties":false,"description":"Holds when a valid (available, not expired) coupon of the given kind is presented. Does NOT redeem it (use trigger.type = coupon for that).","properties":{"templateIds":{"type":"array","items":{"type":"string","format":"uuid"}},"campaignIds":{"type":"array","items":{"type":"string","format":"uuid"}},"codes":{"type":"array","items":{"type":"string"},"description":"Static promo codes."},"ownedByCustomer":{"type":"boolean","default":false,"description":"The coupon must have been issued to the identified customer."}},"anyOf":[{"required":["templateIds"]},{"required":["campaignIds"]},{"required":["codes"]}]}}},"PaymentMethod":{"properties":{"type":{"const":"payment_method"},"params":{"type":"object","additionalProperties":false,"required":["methods"],"properties":{"methods":{"type":"array","minItems":1,"items":{"type":"string","enum":["cash","card","check","voucher","gift_card","points","mobile","transfer","credit","other"]}},"brands":{"type":"array","items":{"type":"string"}},"minAmountCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"mode":{"type":"string","enum":["any","all_payments","majority"],"default":"any"}}}}},"CounterBalance":{"properties":{"type":{"const":"counter_balance"},"params":{"type":"object","additionalProperties":false,"required":["counterTypeCode"],"properties":{"counterTypeCode":{"type":"string"},"balance":{"type":"string","enum":["available","total","pending"],"default":"available"},"min":{"type":"integer"},"max":{"type":"integer"}},"anyOf":[{"required":["min"]},{"required":["max"]}]}}},"UsageCount":{"properties":{"type":{"const":"usage_count"},"params":{"type":"object","additionalProperties":false,"required":["scope","max"],"description":"Number of previous applications of THIS offer (or another offer via offerId).","properties":{"scope":{"type":"string","enum":["customer","card","store","global"]},"offerId":{"type":"string","format":"uuid"},"period":{"$ref":"#/components/schemas/Offer/$defs/Period","default":"ever"},"periodDays":{"type":"integer","minimum":1},"min":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":0}}}}},"AbVariant":{"properties":{"type":{"const":"ab_variant"},"params":{"type":"object","additionalProperties":false,"required":["testId","variants"],"properties":{"testId":{"type":"string","format":"uuid"},"variants":{"type":"array","minItems":1,"items":{"type":"string","maxLength":20}}}}}},"Event":{"properties":{"type":{"const":"event"},"params":{"type":"object","additionalProperties":false,"description":"Only for trigger.type = event: filter on event payload.","properties":{"path":{"type":"string","description":"JSON path in event.payload (dot notation)."},"op":{"$ref":"#/components/schemas/Offer/$defs/StringOp"},"value":{"type":["string","number","boolean","null"]}},"required":["path","op"]}}},"CustomCel":{"properties":{"type":{"const":"custom_cel"},"params":{"type":"object","additionalProperties":false,"required":["expression"],"description":"CEL boolean expression over variables: ticket, customer, balances (map counterTypeCode -> {available,total}), store, now, ctx. Validated at save time by the hub (engine /mgmt/v1/cel/validate).","properties":{"expression":{"type":"string","maxLength":4000},"lineExpression":{"type":"string","maxLength":4000,"description":"Optional CEL over variable line; when set the criterion is article-level and its eligible set is the lines where lineExpression is true."},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleEan":{"properties":{"type":{"const":"article_ean"},"params":{"type":"object","additionalProperties":false,"required":["eans"],"properties":{"eans":{"type":"array","minItems":1,"maxItems":5000,"items":{"type":"string","pattern":"^[0-9]{8,14}$"}},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleRef":{"properties":{"type":{"const":"article_ref"},"params":{"type":"object","additionalProperties":false,"required":["refs"],"properties":{"refs":{"type":"array","minItems":1,"maxItems":5000,"items":{"type":"string","maxLength":64}},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleList":{"properties":{"type":{"const":"article_list"},"params":{"type":"object","additionalProperties":false,"required":["listIds"],"description":"Product lists managed in the studio (product_lists table); matched on ean or ref.","properties":{"listIds":{"type":"array","minItems":1,"items":{"type":"string","format":"uuid"}},"mode":{"type":"string","enum":["in","not_in"],"default":"in"},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleCategory":{"properties":{"type":{"const":"article_category"},"params":{"type":"object","additionalProperties":false,"required":["codes"],"properties":{"codes":{"type":"array","minItems":1,"items":{"type":"string"}},"mode":{"type":"string","enum":["in","not_in"],"default":"in"},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleRayon":{"properties":{"type":{"const":"article_rayon"},"params":{"type":"object","additionalProperties":false,"required":["codes"],"properties":{"codes":{"type":"array","minItems":1,"items":{"type":"string"}},"mode":{"type":"string","enum":["in","not_in"],"default":"in"},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleUnivers":{"properties":{"type":{"const":"article_univers"},"params":{"type":"object","additionalProperties":false,"required":["codes"],"properties":{"codes":{"type":"array","minItems":1,"items":{"type":"string"}},"mode":{"type":"string","enum":["in","not_in"],"default":"in"},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleBrand":{"properties":{"type":{"const":"article_brand"},"params":{"type":"object","additionalProperties":false,"required":["brands"],"properties":{"brands":{"type":"array","minItems":1,"items":{"type":"string"}},"mode":{"type":"string","enum":["in","not_in"],"default":"in"},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticleAttribute":{"properties":{"type":{"const":"article_attribute"},"params":{"type":"object","additionalProperties":false,"required":["attribute","op"],"properties":{"attribute":{"type":"string","description":"Line field (name, taxRateBp, qtyUnit) or attributes.<key>."},"op":{"$ref":"#/components/schemas/Offer/$defs/StringOp"},"value":{"type":["string","number","boolean","null"]},"values":{"type":"array","items":{"type":["string","number","boolean"]}},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}}}}},"ArticlePrice":{"properties":{"type":{"const":"article_price"},"params":{"type":"object","additionalProperties":false,"properties":{"field":{"type":"string","enum":["unitPriceCents","lineTotalCents"],"default":"unitPriceCents"},"minCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"maxCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"match":{"$ref":"#/components/schemas/Offer/$defs/ArticleMatch"}},"anyOf":[{"required":["minCents"]},{"required":["maxCents"]}]}}},"ArticleLot":{"properties":{"type":{"const":"article_lot"},"params":{"type":"object","additionalProperties":false,"required":["components"],"description":"A lot (bundle) is complete when every component is satisfied by distinct units. The criterion holds when at least minLots complete lots exist; its eligible set is the units consumed by the lots (cheapest-first allocation).","properties":{"components":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","additionalProperties":false,"required":["qty"],"properties":{"criterionId":{"$ref":"#/components/schemas/Offer/$defs/NodeId","description":"Article criterion elsewhere in the tree whose eligible set feeds this component."},"selector":{"$ref":"#/components/schemas/Offer/$defs/Criterion","description":"Inline article criterion (alternative to criterionId)."},"qty":{"type":"number","exclusiveMinimum":0}},"oneOf":[{"required":["criterionId"]},{"required":["selector"]}]}},"minLots":{"type":"integer","minimum":1,"default":1},"maxLots":{"type":"integer","minimum":1,"description":"Cap on lots per ticket (default unlimited)."},"sameArticle":{"type":"boolean","default":false,"description":"All units of a lot must share the same ean/ref."}}}}}},"Target":{"type":"object","additionalProperties":false,"description":"Which ticket lines an article-level action applies to. Exactly one of all / criterionId / selector.","properties":{"all":{"const":true,"description":"All lines without flag no_promo."},"criterionId":{"$ref":"#/components/schemas/Offer/$defs/NodeId","description":"Eligible set of an article criterion of this offer."},"selector":{"$ref":"#/components/schemas/Offer/$defs/Criterion","description":"Inline article criterion."},"sort":{"type":"string","enum":["cheapest_first","most_expensive_first","ticket_order"],"default":"cheapest_first"},"maxQty":{"type":"number","exclusiveMinimum":0,"description":"Limit the number of units affected."}},"oneOf":[{"required":["all"]},{"required":["criterionId"]},{"required":["selector"]}]},"ActionType":{"type":"string","enum":["discount_amount","discount_percent","discount_fixed_price","cheapest_free","free_product","lot_price","points_fixed","points_per_amount","points_multiplier","counter_update","coupon_issue","message","customer_tag","customer_status","custom_event","stamp_card"]},"ActionBase":{"type":"object","additionalProperties":false,"required":["type","params"],"properties":{"id":{"$ref":"#/components/schemas/Offer/$defs/NodeId"},"type":{"$ref":"#/components/schemas/Offer/$defs/ActionType"},"label":{"type":"string","maxLength":120},"params":{"type":"object"}}},"Action":{"allOf":[{"$ref":"#/components/schemas/Offer/$defs/ActionBase"},{"oneOf":[{"$ref":"#/components/schemas/Offer/$defs/actions/DiscountAmount"},{"$ref":"#/components/schemas/Offer/$defs/actions/DiscountPercent"},{"$ref":"#/components/schemas/Offer/$defs/actions/DiscountFixedPrice"},{"$ref":"#/components/schemas/Offer/$defs/actions/CheapestFree"},{"$ref":"#/components/schemas/Offer/$defs/actions/FreeProduct"},{"$ref":"#/components/schemas/Offer/$defs/actions/LotPrice"},{"$ref":"#/components/schemas/Offer/$defs/actions/PointsFixed"},{"$ref":"#/components/schemas/Offer/$defs/actions/PointsPerAmount"},{"$ref":"#/components/schemas/Offer/$defs/actions/PointsMultiplier"},{"$ref":"#/components/schemas/Offer/$defs/actions/CounterUpdate"},{"$ref":"#/components/schemas/Offer/$defs/actions/CouponIssue"},{"$ref":"#/components/schemas/Offer/$defs/actions/Message"},{"$ref":"#/components/schemas/Offer/$defs/actions/CustomerTag"},{"$ref":"#/components/schemas/Offer/$defs/actions/CustomerStatus"},{"$ref":"#/components/schemas/Offer/$defs/actions/CustomEvent"},{"$ref":"#/components/schemas/Offer/$defs/actions/StampCard"}]}]},"actions":{"DiscountAmount":{"properties":{"type":{"const":"discount_amount"},"params":{"type":"object","additionalProperties":false,"required":["amountCents"],"description":"Fixed discount. scope ticket: one amount on the ticket (capped at the eligible total). per_line / per_unit: on each targeted line / unit. eligible_total: one amount spread pro rata on the targeted lines.","properties":{"amountCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"scope":{"type":"string","enum":["ticket","eligible_total","per_line","per_unit"],"default":"ticket"},"target":{"$ref":"#/components/schemas/Offer/$defs/Target","description":"Required when scope != ticket."},"maxTotalCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"}}}}},"DiscountPercent":{"properties":{"type":{"const":"discount_percent"},"params":{"type":"object","additionalProperties":false,"required":["percentBp"],"description":"Percentage discount. scope ticket: on the eligible ticket amount (spread pro rata over eligible lines in the breakdown). lines: on the targeted lines.","properties":{"percentBp":{"type":"integer","minimum":1,"maximum":10000},"scope":{"type":"string","enum":["ticket","lines"],"default":"ticket"},"target":{"$ref":"#/components/schemas/Offer/$defs/Target"},"maxTotalCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"rounding":{"$ref":"#/components/schemas/Offer/$defs/Rounding"}}}}},"DiscountFixedPrice":{"properties":{"type":{"const":"discount_fixed_price"},"params":{"type":"object","additionalProperties":false,"required":["priceCents","target"],"description":"Bring the targeted units to a fixed price (discount = unitPrice - price when positive).","properties":{"priceCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"target":{"$ref":"#/components/schemas/Offer/$defs/Target"}}}}},"CheapestFree":{"properties":{"type":{"const":"cheapest_free"},"params":{"type":"object","additionalProperties":false,"required":["target","groupSize","freeCount"],"description":"\"N bought, M cheapest free\" (or discounted by percentBp). Units of the target are sorted by unit price, grouped by groupSize; in each group the freeCount cheapest units get the discount.","properties":{"target":{"$ref":"#/components/schemas/Offer/$defs/Target"},"groupSize":{"type":"integer","minimum":2},"freeCount":{"type":"integer","minimum":1},"percentBp":{"type":"integer","minimum":1,"maximum":10000,"default":10000},"maxGroups":{"type":"integer","minimum":1}}}}},"FreeProduct":{"properties":{"type":{"const":"free_product"},"params":{"type":"object","additionalProperties":false,"required":["product","qty","mode"],"description":"Offer a product. add_line: the result asks the POS to add the line at price 0 (flag remizo_free_product). discount_if_present: 100% discount on the product if already in the ticket, otherwise the action is skipped (reason free_product_absent).","properties":{"product":{"type":"object","additionalProperties":false,"properties":{"ean":{"type":"string","pattern":"^[0-9]{8,14}$"},"ref":{"type":"string","maxLength":64},"name":{"type":"string","maxLength":200}},"anyOf":[{"required":["ean"]},{"required":["ref"]}]},"qty":{"type":"integer","minimum":1,"default":1},"mode":{"type":"string","enum":["add_line","discount_if_present"]},"perLotOf":{"$ref":"#/components/schemas/Offer/$defs/NodeId","description":"Repeat per complete lot of this article_lot criterion."}}}}},"LotPrice":{"properties":{"type":{"const":"lot_price"},"params":{"type":"object","additionalProperties":false,"required":["lotCriterionId","lotPriceCents"],"description":"Set the price of each complete lot (discount = lot units total - lotPriceCents, spread pro rata).","properties":{"lotCriterionId":{"$ref":"#/components/schemas/Offer/$defs/NodeId"},"lotPriceCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"maxLots":{"type":"integer","minimum":1}}}}},"PointsFixed":{"properties":{"type":{"const":"points_fixed"},"params":{"type":"object","additionalProperties":false,"required":["counterTypeCode","amount"],"description":"Credit a fixed number of units on a counter (points, cashback cents, visits...).","properties":{"counterTypeCode":{"type":"string"},"amount":{"type":"integer","minimum":1},"activationDelayDays":{"type":"integer","minimum":0,"default":0},"expiry":{"$ref":"#/components/schemas/Offer/$defs/Expiry"}}}}},"PointsPerAmount":{"properties":{"type":{"const":"points_per_amount"},"params":{"type":"object","additionalProperties":false,"required":["counterTypeCode","points","perCents"],"description":"Credit `points` units for every `perCents` of the base amount (e.g. 1 point per 100 cents).","properties":{"counterTypeCode":{"type":"string"},"points":{"type":"integer","minimum":1},"perCents":{"type":"integer","minimum":1},"base":{"type":"string","enum":["gross","pre_tax","eligible","after_discounts","target_lines"],"default":"after_discounts","description":"after_discounts = eligible amount minus Remizo discounts of lower or equal levels."},"target":{"$ref":"#/components/schemas/Offer/$defs/Target","description":"Required when base = target_lines."},"rounding":{"$ref":"#/components/schemas/Offer/$defs/Rounding"},"minBaseCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"maxPoints":{"type":"integer","minimum":1},"activationDelayDays":{"type":"integer","minimum":0,"default":0},"expiry":{"$ref":"#/components/schemas/Offer/$defs/Expiry"}}}}},"PointsMultiplier":{"properties":{"type":{"const":"points_multiplier"},"params":{"type":"object","additionalProperties":false,"required":["counterTypeCode","multiplierBp"],"description":"Multiply the units credited on this ticket by other offers (lower levels only). 20000 = x2.","properties":{"counterTypeCode":{"type":"string"},"multiplierBp":{"type":"integer","minimum":1,"maximum":1000000},"appliesTo":{"type":"string","enum":["all_earned","offer_ids"],"default":"all_earned"},"offerIds":{"type":"array","items":{"type":"string","format":"uuid"}},"rounding":{"$ref":"#/components/schemas/Offer/$defs/Rounding"}}}}},"CounterUpdate":{"properties":{"type":{"const":"counter_update"},"params":{"type":"object","additionalProperties":false,"required":["counterTypeCode","op"],"description":"Technical counter operation (visits, stamps...). decrement fails the action (not the offer) when insufficient unless allowNegative on the counter type.","properties":{"counterTypeCode":{"type":"string"},"op":{"type":"string","enum":["increment","decrement","set","reset"]},"amount":{"type":"integer","minimum":0},"expiry":{"$ref":"#/components/schemas/Offer/$defs/Expiry"}}}}},"CouponIssue":{"properties":{"type":{"const":"coupon_issue"},"params":{"type":"object","additionalProperties":false,"required":["templateId"],"description":"Issue a coupon to the customer (or anonymous ticket coupon when no customer). Rendering (png/pdf) happens asynchronously on the hub.","properties":{"templateId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1,"maximum":10,"default":1},"validity":{"type":"object","additionalProperties":false,"required":["mode"],"properties":{"mode":{"type":"string","enum":["template_default","fixed","rolling"]},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"startDelayDays":{"type":"integer","minimum":0,"default":0},"days":{"type":"integer","minimum":1}}},"render":{"type":"string","enum":["inherit","none","png","pdf"],"default":"inherit","description":"inherit = offer.coupon.render."},"faceValue":{"type":"object","additionalProperties":false,"required":["kind","value"],"description":"Value printed on the coupon (informational; the discount itself is carried by the offer triggered by this coupon).","properties":{"kind":{"type":"string","enum":["amount_cents","percent_bp","points","text"]},"value":{"type":["integer","string"]}}},"linkedOfferId":{"type":"string","format":"uuid","description":"Offer (trigger.type = coupon) that this coupon unlocks; printed on the coupon."}}}}},"Message":{"properties":{"type":{"const":"message"},"params":{"type":"object","additionalProperties":false,"required":["text"],"description":"Text returned to the POS. Placeholders: {{customer.firstName}}, {{balance.<counterTypeCode>.available}}, {{offer.name}}, {{discount.total}}, {{points.earned}}.","properties":{"placement":{"type":"string","enum":["ticket","screen","both"],"default":"ticket"},"text":{"$ref":"#/components/schemas/Offer/$defs/I18nText"},"onlyWhenApplied":{"type":"boolean","default":true}}}}},"CustomerTag":{"properties":{"type":{"const":"customer_tag"},"params":{"type":"object","additionalProperties":false,"required":["tag","op"],"properties":{"tag":{"type":"string","maxLength":40},"op":{"type":"string","enum":["add","remove"]}}}}},"CustomerStatus":{"properties":{"type":{"const":"customer_status"},"params":{"type":"object","additionalProperties":false,"required":["status"],"description":"Set the loyalty status (tier). Applied at commit.","properties":{"status":{"type":"string","maxLength":32},"onlyUpgrade":{"type":"boolean","default":true}}}}},"CustomEvent":{"properties":{"type":{"const":"custom_event"},"params":{"type":"object","additionalProperties":false,"required":["eventType"],"description":"Emit a customer event (customer_events table + RabbitMQ custom.event) usable by event-triggered offers and webhooks.","properties":{"eventType":{"type":"string","maxLength":64},"payload":{"type":"object","additionalProperties":true}}}}},"StampCard":{"properties":{"type":{"const":"stamp_card"},"params":{"type":"object","additionalProperties":false,"required":["counterTypeCode","stampsRequired","reward"],"description":"Stamp card: adds stampsPerVisit stamps (once per ticket) to the counter; each time the card is full (stampsRequired), the reward is granted and the card restarts with the extra stamps.","properties":{"counterTypeCode":{"type":"string","description":"Counter holding the stamps of the current card (kind visits)."},"stampsRequired":{"type":"integer","minimum":2,"maximum":50},"stampsPerVisit":{"type":"integer","minimum":1,"maximum":10,"default":1},"reward":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","enum":["coupon","points"]},"templateId":{"type":"string","format":"uuid","description":"Coupon template (type coupon)."},"quantity":{"type":"integer","minimum":1,"maximum":10,"default":1},"counterTypeCode":{"type":"string","description":"Points counter credited (type points)."},"amount":{"type":"integer","minimum":1,"description":"Points credited per full card (type points)."},"label":{"type":"string","maxLength":120,"description":"Reward shown on the card (e.g. a free coffee)."}}},"design":{"type":"object","additionalProperties":false,"description":"Card look (studio, POS, customer apps).","properties":{"title":{"type":"string","maxLength":60},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"accent":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"icon":{"type":"string","maxLength":32}}}}}}}},"Stacking":{"type":"object","additionalProperties":false,"required":["level","mode"],"description":"Levels run in ascending order; level N sees the ticket after discounts of levels < N. Within one level: offers in mode cumulative all apply; among offers in mode best_offer only the one with the highest total value (discount cents, then points) applies, the others are rejected with reason lost_best_offer; offers in mode best_line compete per line: on each line only the best discount wins (lost_best_line). Priority breaks ties (higher first) and orders cumulative execution.","properties":{"level":{"type":"integer","minimum":0,"maximum":99},"mode":{"type":"string","enum":["cumulative","best_offer","best_line"]},"priority":{"type":"integer","minimum":0,"maximum":1000,"default":100},"exclusions":{"type":"array","uniqueItems":true,"items":{"type":"string","format":"uuid"},"description":"Offer ids that cannot apply on the same ticket as this one. Symmetric: the engine treats A excludes B as B excludes A; the lower-priority (or later-level) one is rejected with reason excluded_by."},"exclusiveTags":{"type":"array","uniqueItems":true,"items":{"type":"string","maxLength":40},"description":"Offers sharing one of these tags are mutually exclusive on a ticket."},"exclusive":{"type":"boolean","default":false,"description":"When applied, no other offer of a higher level applies on this ticket."}}},"Limits":{"type":"object","additionalProperties":false,"description":"Budgets. Counters are checked at evaluation and consumed at commit (see architecture.md, limits).","properties":{"global":{"type":"object","additionalProperties":false,"properties":{"maxUses":{"type":"integer","minimum":1,"description":"Total number of tickets the offer may apply to."},"maxAmountCents":{"$ref":"#/components/schemas/Offer/$defs/Cents","description":"Total discount budget."},"maxPoints":{"type":"integer","minimum":1},"maxCoupons":{"type":"integer","minimum":1},"onExhausted":{"type":"string","enum":["reject","suspend"],"default":"reject","description":"suspend = also flip the offer status to suspended (event offer.budget_exhausted)."}}},"perCustomer":{"type":"object","additionalProperties":false,"properties":{"maxUses":{"type":"integer","minimum":1},"maxAmountCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"},"period":{"$ref":"#/components/schemas/Offer/$defs/Period","default":"ever"},"periodDays":{"type":"integer","minimum":1},"identifyBy":{"type":"string","enum":["customer","card"],"default":"customer"},"whenAnonymous":{"type":"string","enum":["allow","reject"],"default":"allow","description":"Behaviour when the ticket has no identified customer."}}},"perTicket":{"type":"object","additionalProperties":false,"properties":{"maxApplications":{"type":"integer","minimum":1,"default":1,"description":"How many times the offer may repeat in one ticket (e.g. once per lot)."},"maxDiscountCents":{"$ref":"#/components/schemas/Offer/$defs/Cents"}}},"perStore":{"type":"object","additionalProperties":false,"properties":{"maxUses":{"type":"integer","minimum":1},"period":{"$ref":"#/components/schemas/Offer/$defs/Period","default":"ever"},"periodDays":{"type":"integer","minimum":1}}}}},"Validity":{"type":"object","additionalProperties":false,"required":["from"],"description":"Hard activation window; outside it the engine does not even load the offer (reason not_in_validity when evaluated through the simulator). Finer schedules use date_range / time_range / weekdays criteria.","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":["string","null"],"format":"date-time","description":"null = open-ended."},"timezone":{"type":"string","description":"IANA timezone for calendar computations; default workspace timezone."}}},"CouponRenderDefaults":{"type":"object","additionalProperties":false,"required":["render"],"description":"Default rendering for coupons issued by this offer. none = code only (default).","properties":{"render":{"type":"string","enum":["none","png","pdf"],"default":"none"},"templateId":{"type":["string","null"],"format":"uuid","description":"Coupon template whose design is rendered. Required when render != none."}},"if":{"properties":{"render":{"enum":["png","pdf"]}}},"then":{"required":["templateId"]}}}},"EvaluationResult":{"title":"EvaluationResult","description":"Result returned by the engine for simulate / evaluate / commit / refund calls. Everything the POS needs to adjust the ticket (discounts per line), print (messages, coupons) and display (balances), plus the explanation of every offer considered. Amounts are integer cents.","type":"object","additionalProperties":false,"required":["evaluationId","mode","status","ticketKey","totals","applied","rejected","pointsMovements","balances","couponsIssued","couponsRedeemed","messages","warnings","engine"],"properties":{"schemaVersion":{"type":"string","const":"1"},"evaluationId":{"type":"string","format":"uuid","description":"Unique id of this computation (also the trace id suffix)."},"transactionId":{"type":["string","null"],"format":"uuid","description":"Persisted transaction id (null in simulate mode)."},"ticketKey":{"type":"string","description":"Canonical key <storeCode>:<posId>:<businessDate>:<ticketNumber>."},"mode":{"type":"string","enum":["simulate","evaluate","commit","refund"]},"status":{"type":"string","enum":["ok","ok_with_warnings","customer_unknown","rejected"],"description":"rejected is only used by commit when the pending transaction cannot be committed (see problem details); evaluation errors are HTTP errors, not results."},"transactionState":{"type":["string","null"],"enum":["pending","committed","cancelled","refunded","partially_refunded","expired",null]},"idempotentReplay":{"type":"boolean","default":false,"description":"true when this response was served from the idempotency store."},"customer":{"$ref":"#/components/schemas/EvaluationResult/$defs/CustomerResolution"},"totals":{"$ref":"#/components/schemas/EvaluationResult/$defs/Totals"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/LineAdjustment"},"description":"Per-line net effect (all offers merged). Lines with no adjustment are omitted."},"addedLines":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/AddedLine"},"description":"Lines the POS must add (free_product mode add_line)."},"applied":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/AppliedOffer"}},"rejected":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/RejectedOffer"},"description":"Present when options.explain is true (default)."},"pointsMovements":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/PointsMovement"}},"balances":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/Balance"},"description":"Balances after this ticket (projected in simulate/evaluate, actual in commit)."},"couponsIssued":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/CouponIssued"}},"couponsRedeemed":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/CouponRedeemed"}},"couponsRejected":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/CouponRejected"},"description":"Presented codes that could not be used."},"messages":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/Message"}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/Warning"}},"engine":{"$ref":"#/components/schemas/EvaluationResult/$defs/EngineInfo"}},"$defs":{"Cents":{"type":"integer"},"CustomerResolution":{"type":"object","additionalProperties":false,"required":["resolved"],"properties":{"resolved":{"type":"boolean"},"customerId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid"},"cardNumber":{"type":"string"},"externalRef":{"type":"string"},"status":{"type":"string","enum":["active","blocked","prospect","anonymized"]},"loyaltyStatus":{"type":"string"},"firstName":{"type":"string"},"created":{"type":"boolean","description":"true when the card/customer was auto-created by this call."},"matchedIdentifier":{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string"},"value":{"type":"string"}}},"segments":{"type":"array","items":{"type":"string","format":"uuid"}}}},"Totals":{"type":"object","additionalProperties":false,"required":["grossCents","discountCents","netCents"],"properties":{"grossCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"Ticket gross as received."},"discountCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"Total Remizo discount (ticket-level + line-level)."},"ticketDiscountCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"Part granted as ticket-level discount (not attributable to one line by the POS)."},"lineDiscountCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"Part granted on lines (sum of lines[].discountCents)."},"netCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"grossCents - discountCents. Informational; the POS remains the source of truth."},"pointsBurnCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"Value of counter units burnt as payment (payments method points / options.pointsBurn)."},"currency":{"type":"string","pattern":"^[A-Z]{3}$"}}},"LineAdjustment":{"type":"object","additionalProperties":false,"required":["lineNo","discountCents"],"properties":{"lineNo":{"type":"integer","minimum":1},"discountCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"Total Remizo discount on the line."},"qtyAffected":{"type":"number","description":"Units concerned (for N bought M free)."},"newLineTotalCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents"},"pointsEarned":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["counterTypeCode","amount"],"properties":{"counterTypeCode":{"type":"string"},"amount":{"type":"integer"}}}},"offers":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["offerId","discountCents"],"properties":{"offerId":{"type":"string","format":"uuid"},"actionId":{"type":"string"},"discountCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents"},"qtyAffected":{"type":"number"}}}}}},"AddedLine":{"type":"object","additionalProperties":false,"required":["offerId","qty","unitPriceCents"],"properties":{"offerId":{"type":"string","format":"uuid"},"actionId":{"type":"string"},"ean":{"type":"string"},"ref":{"type":"string"},"name":{"type":"string"},"qty":{"type":"integer","minimum":1},"unitPriceCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","const":0},"flags":{"type":"array","items":{"type":"string"},"default":["remizo_free_product"]}}},"AppliedOffer":{"type":"object","additionalProperties":false,"required":["offerId","offerCode","offerVersion","name","level","mode","applications","valueCents","actions"],"properties":{"offerId":{"type":"string","format":"uuid"},"offerCode":{"type":"string"},"offerVersion":{"type":"integer"},"name":{"type":"string"},"level":{"type":"integer"},"mode":{"type":"string","enum":["cumulative","best_offer","best_line"]},"priority":{"type":"integer"},"applications":{"type":"integer","minimum":1,"description":"Times the offer repeated in the ticket (limits.perTicket)."},"valueCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents","description":"Total discount value of this offer (0 for pure points/coupon offers)."},"triggeredBy":{"type":"object","additionalProperties":false,"properties":{"couponCode":{"type":"string"},"eventId":{"type":"string","format":"uuid"},"abTestId":{"type":"string","format":"uuid"},"abVariant":{"type":"string"}}},"actions":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationResult/$defs/ActionResult"}},"explanation":{"type":"string","description":"Human-readable (workspace locale) summary, e.g. \"Ticket >= 50,00 EUR and store in group IDF: -10%\"."},"criteriaTrace":{"type":"array","description":"Simulator / explain mode only: evaluation of each criterion node.","items":{"type":"object","additionalProperties":false,"required":["nodeId","type","result"],"properties":{"nodeId":{"type":"string"},"type":{"type":"string"},"result":{"type":"boolean"},"matchedLines":{"type":"array","items":{"type":"integer"}},"detail":{"type":"string"}}}}}},"ActionResult":{"type":"object","additionalProperties":false,"required":["type","status"],"properties":{"actionId":{"type":"string"},"type":{"type":"string","enum":["discount_amount","discount_percent","discount_fixed_price","cheapest_free","free_product","lot_price","points_fixed","points_per_amount","points_multiplier","counter_update","coupon_issue","message","customer_tag","customer_status","custom_event","stamp_card"]},"status":{"type":"string","enum":["applied","skipped","deferred"],"description":"deferred = will happen at commit (coupon render, customer status...)."},"skipReason":{"type":"string","enum":["free_product_absent","no_eligible_lines","insufficient_counter","cap_reached","no_customer","zero_value"]},"discountCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents"},"lines":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["lineNo","discountCents"],"properties":{"lineNo":{"type":"integer"},"discountCents":{"$ref":"#/components/schemas/EvaluationResult/$defs/Cents"},"qtyAffected":{"type":"number"}}}},"counterTypeCode":{"type":"string"},"amount":{"type":"integer","description":"Counter units credited/debited by this action."},"couponIds":{"type":"array","items":{"type":"string","format":"uuid"}},"messageIndex":{"type":"integer","description":"Index into messages[]."},"detail":{"type":"string"},"stampCard":{"type":"object","additionalProperties":false,"required":["stampsRequired","stampsBefore","stampsAdded","stampsAfter","completed"],"description":"Stamp card progress (type stamp_card): stamps on the current card before and after the ticket, completed cards.","properties":{"stampsRequired":{"type":"integer"},"stampsBefore":{"type":"integer"},"stampsAdded":{"type":"integer"},"stampsAfter":{"type":"integer"},"completed":{"type":"integer"}}}}},"RejectedOffer":{"type":"object","additionalProperties":false,"required":["offerId","offerCode","name","reason"],"properties":{"offerId":{"type":"string","format":"uuid"},"offerCode":{"type":"string"},"offerVersion":{"type":"integer"},"name":{"type":"string"},"level":{"type":"integer"},"reason":{"$ref":"#/components/schemas/EvaluationResult/$defs/RejectReason"},"criterionId":{"type":"string","description":"First failing criterion node (reason criteria_not_met)."},"criterionType":{"type":"string"},"byOfferId":{"type":"string","format":"uuid","description":"Winning/excluding offer (reasons lost_best_offer, lost_best_line, excluded_by, exclusive_applied)."},"detail":{"type":"string"},"criteriaTrace":{"$ref":"#/components/schemas/EvaluationResult/$defs/AppliedOffer/properties/criteriaTrace"}}},"RejectReason":{"type":"string","enum":["criteria_not_met","not_in_validity","trigger_mismatch","customer_required","customer_blocked","excluded_by","exclusive_applied","lost_best_offer","lost_best_line","global_limit_reached","customer_limit_reached","store_limit_reached","ticket_limit_reached","coupon_required","coupon_invalid","coupon_expired","coupon_already_used","coupon_not_owned","ab_not_in_variant","suspended","no_eligible_lines","zero_value","action_error"]},"PointsMovement":{"type":"object","additionalProperties":false,"required":["counterTypeCode","kind","amount","state"],"properties":{"movementId":{"type":["string","null"],"format":"uuid","description":"Ledger id (null until commit)."},"counterTypeCode":{"type":"string"},"kind":{"type":"string","enum":["earn","burn","reserve","release","adjust","expire","reset"]},"amount":{"type":"integer","description":"Signed: positive credit, negative debit."},"state":{"type":"string","enum":["projected","pending_activation","posted","reserved","confirmed","cancelled"]},"offerId":{"type":["string","null"],"format":"uuid"},"reservationId":{"type":["string","null"],"format":"uuid"},"activatesAt":{"type":["string","null"],"format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time"},"reason":{"type":"string"}}},"Balance":{"type":"object","additionalProperties":false,"required":["counterTypeCode","name","unit","total","available"],"properties":{"counterTypeCode":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string","enum":["points","cashback","visits","technical"]},"unit":{"type":"string","description":"pts, EUR cents, visits..."},"total":{"type":"integer"},"available":{"type":"integer","description":"total - reserved - pending_activation."},"reserved":{"type":"integer"},"pending":{"type":"integer","description":"Credited but not yet active."},"previousTotal":{"type":"integer","description":"Before this ticket."},"nextExpiry":{"type":["object","null"],"additionalProperties":false,"properties":{"amount":{"type":"integer"},"at":{"type":"string","format":"date-time"}}}}},"CouponIssued":{"type":"object","additionalProperties":false,"required":["couponId","code","format","templateId","validFrom","validTo","render"],"properties":{"couponId":{"type":"string","format":"uuid"},"code":{"type":"string"},"format":{"type":"string","enum":["ean13","qr","short"]},"templateId":{"type":"string","format":"uuid"},"templateName":{"type":"string"},"offerId":{"type":"string","format":"uuid"},"linkedOfferId":{"type":["string","null"],"format":"uuid"},"faceValue":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string"},"value":{"type":["integer","string"]},"label":{"type":"string"}}},"validFrom":{"type":"string","format":"date-time"},"validTo":{"type":"string","format":"date-time"},"state":{"type":"string","enum":["projected","available"],"description":"projected until commit."},"render":{"type":"object","additionalProperties":false,"required":["mode","status"],"properties":{"mode":{"type":"string","enum":["none","png","pdf"]},"status":{"type":"string","enum":["none","pending","ready","failed"]},"url":{"type":"string","format":"uri","description":"Signed hub URL (/v1/public/coupons/{token}); valid even while pending (returns 202 until ready)."}}},"printText":{"type":"string","description":"Short text to print under the barcode."}}},"CouponRedeemed":{"type":"object","additionalProperties":false,"required":["code","couponId","offerId","state"],"properties":{"code":{"type":"string"},"couponId":{"type":"string","format":"uuid"},"offerId":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["reserved","used"],"description":"reserved after evaluate, used after commit."}}},"CouponRejected":{"type":"object","additionalProperties":false,"required":["code","reason"],"properties":{"code":{"type":"string"},"reason":{"type":"string","enum":["unknown","expired","not_yet_valid","already_used","reserved_elsewhere","cancelled","not_owned","no_matching_offer","offer_criteria_not_met"]},"offerId":{"type":["string","null"],"format":"uuid"}}},"Message":{"type":"object","additionalProperties":false,"required":["text","placement"],"properties":{"text":{"type":"string"},"placement":{"type":"string","enum":["ticket","screen","both"]},"offerId":{"type":["string","null"],"format":"uuid"},"kind":{"type":"string","enum":["offer","balance","coupon","system"],"default":"offer"}}},"Warning":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","enum":["coherence_lines_vs_total","coherence_item_count","unknown_store","currency_mismatch","customer_auto_created","customer_blocked","payments_incomplete","offer_load_stale","limit_near_exhaustion","coupon_render_disabled","unknown_counter_type","line_without_identifier","duplicate_line_no","points_reservation_mismatch"]},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}},"EngineInfo":{"type":"object","additionalProperties":false,"required":["instance","offersLoadedAt","computeMs"],"properties":{"instance":{"type":"string","description":"Container name, e.g. remizo-acme-engine-1."},"version":{"type":"string"},"offersLoadedAt":{"type":"string","format":"date-time"},"offersCount":{"type":"integer"},"computeMs":{"type":"number"},"totalMs":{"type":"number"}}}}},"Ticket":{"title":"Ticket","description":"Canonical Remizo ticket. Every input adapter (native API, WsOne, future POS vendors) maps its payload to this document before evaluation. All amounts are integer cents (int64) in the ticket currency; the POS computes totals, the engine never recomputes them (coherence check = warning only).","type":"object","additionalProperties":false,"required":["header","lines"],"properties":{"schemaVersion":{"type":"string","const":"1","description":"Ticket document version. Defaults to \"1\" when absent."},"header":{"$ref":"#/components/schemas/Ticket/$defs/Header"},"customer":{"$ref":"#/components/schemas/Ticket/$defs/Customer"},"lines":{"type":"array","minItems":0,"maxItems":2000,"items":{"$ref":"#/components/schemas/Ticket/$defs/Line"}},"payments":{"type":"array","items":{"$ref":"#/components/schemas/Ticket/$defs/Payment"},"description":"Payments announced by the POS. May be empty at evaluation time (before the customer pays) and completed at commit."},"coupons":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/Ticket/$defs/PresentedCoupon"},"description":"Coupon / promo codes presented at checkout."},"options":{"$ref":"#/components/schemas/Ticket/$defs/Options"},"ext":{"type":"object","description":"Free-form extension bag preserved verbatim; reachable from CEL custom criteria as ticket.ext.*. Adapters put vendor-specific fields here.","additionalProperties":true}},"$defs":{"Cents":{"type":"integer","minimum":-9223372036854776000,"maximum":9223372036854776000,"description":"Integer amount in minor currency units (cents)."},"NonNegativeCents":{"type":"integer","minimum":0,"maximum":9223372036854776000},"Header":{"type":"object","additionalProperties":false,"required":["ticketNumber","storeCode","posId","businessDate","timestamp","currency","totals"],"properties":{"ticketNumber":{"type":"string","minLength":1,"maxLength":64,"description":"Receipt number as printed by the POS. Unique per (storeCode, posId, businessDate)."},"storeCode":{"type":"string","minLength":1,"maxLength":32,"description":"Store identifier known to Remizo (stores table)."},"posId":{"type":"string","minLength":1,"maxLength":32,"description":"Cash register identifier (number or IP)."},"cashierId":{"type":"string","maxLength":64},"businessDate":{"type":"string","format":"date","description":"Business day of the ticket (YYYY-MM-DD) in the store timezone."},"timestamp":{"type":"string","format":"date-time","description":"Ticket date/time, RFC 3339 with offset."},"channel":{"$ref":"#/components/schemas/Ticket/$defs/Channel"},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"ISO 4217 code. Must equal the workspace currency."},"locale":{"type":"string","pattern":"^[a-z]{2}(-[A-Z]{2})?$","description":"Used to pick message translations (default: workspace locale)."},"totals":{"$ref":"#/components/schemas/Ticket/$defs/Totals"},"externalRef":{"type":"string","maxLength":128,"description":"Opaque reference from the calling system (order id, basket id)."},"originalTicketKey":{"type":"string","description":"For refund tickets: the ticketKey of the original transaction (see architecture.md, ticket key)."},"kind":{"type":"string","enum":["sale","refund"],"default":"sale"}}},"Totals":{"type":"object","additionalProperties":false,"required":["grossCents"],"description":"Totals as computed by the POS BEFORE Remizo offers. gross = sum of line totals after POS-side discounts.","properties":{"grossCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents","description":"Amount all taxes included, before Remizo offers."},"preTaxCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents"},"taxCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents"},"posDiscountCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents","description":"Discounts already granted by the POS itself (not Remizo)."},"itemCount":{"type":"integer","minimum":0,"description":"Number of units announced by the POS (used by the coherence check)."}}},"Channel":{"type":"string","enum":["pos","web","app","kiosk","callcenter","marketplace","other"],"default":"pos"},"Customer":{"type":"object","additionalProperties":false,"required":["identifiers"],"properties":{"identifiers":{"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/components/schemas/Ticket/$defs/Identifier"},"description":"Resolved in order; the first identifier that matches a known customer wins."},"autoCreateCard":{"type":"boolean","default":true,"description":"When an identifier of type card is unknown, create the card and a minimal customer (workspace setting customers.autoCreateCard can forbid it)."},"prospect":{"type":"object","description":"Minimal data supplied at checkout when a new customer/prospect is created.","additionalProperties":false,"properties":{"email":{"type":"string","format":"email"},"phone":{"type":"string","maxLength":32},"firstName":{"type":"string","maxLength":100},"lastName":{"type":"string","maxLength":100},"optinMarketing":{"type":"boolean"}}}}},"Identifier":{"type":"object","additionalProperties":false,"required":["type","value"],"properties":{"type":{"$ref":"#/components/schemas/Ticket/$defs/IdentifierType"},"value":{"type":"string","minLength":1,"maxLength":128}}},"IdentifierType":{"type":"string","description":"card = loyalty card number (WsOne type 1); web_account = e-commerce account (WsOne 10); wallet = mobile wallet id (WsOne 30); prospect = prospect id (WsOne 99); customer = Remizo customer UUID; account = Remizo account id; external = CRM external reference.","enum":["card","web_account","wallet","prospect","customer","account","email","phone","external"]},"Line":{"type":"object","additionalProperties":false,"required":["lineNo","qty","unitPriceCents","lineTotalCents"],"properties":{"lineNo":{"type":"integer","minimum":1,"description":"Position in the ticket, unique within the ticket. Used to address discounts in the result."},"ean":{"type":"string","pattern":"^[0-9]{8,14}$","description":"EAN-8/13/14 or GTIN."},"ref":{"type":"string","maxLength":64,"description":"Retailer main reference (SKU)."},"name":{"type":"string","maxLength":200},"category":{"type":"string","maxLength":64,"description":"Finest classification level (famille / sous-famille)."},"rayon":{"type":"string","maxLength":64,"description":"Department (rayon)."},"univers":{"type":"string","maxLength":64,"description":"Top classification level (univers)."},"brand":{"type":"string","maxLength":100},"qty":{"type":"number","exclusiveMinimum":0,"multipleOf":0.001,"description":"Quantity; integer for unit items, up to 3 decimals for weight/volume items. Refund tickets use positive quantities with header.kind = refund."},"qtyUnit":{"type":"string","enum":["unit","kg","g","l","ml","m"],"default":"unit"},"unitPriceCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents","description":"Unit price all taxes included, before POS discounts."},"unitPricePreTaxCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents"},"posDiscountCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents","default":0,"description":"Discount already applied by the POS on this line (total for the line)."},"lineTotalCents":{"$ref":"#/components/schemas/Ticket/$defs/NonNegativeCents","description":"qty x unitPriceCents - posDiscountCents, as computed by the POS. Remizo offers apply on this amount."},"taxRateBp":{"type":"integer","minimum":0,"maximum":10000,"description":"VAT rate in basis points (2000 = 20%)."},"flags":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["no_promo","no_points","gift_card","service","deposit","fee","already_discounted","remizo_free_product"]},"description":"no_promo: excluded from discounts; no_points: excluded from points base; remizo_free_product: line added by the POS following a free_product action (ignored on re-evaluation)."},"attributes":{"type":"object","description":"Retailer-specific attributes (size, color, supplier...). Reachable by article_attribute criteria.","additionalProperties":{"type":["string","number","boolean"]}}}},"Payment":{"type":"object","additionalProperties":false,"required":["method","amountCents"],"properties":{"method":{"$ref":"#/components/schemas/Ticket/$defs/PaymentMethod"},"amountCents":{"$ref":"#/components/schemas/Ticket/$defs/Cents"},"reference":{"type":"string","maxLength":128,"description":"Authorization code, voucher number, or Remizo reservation id when method = points."},"brand":{"type":"string","maxLength":32,"description":"Card scheme or wallet brand (visa, cb, applepay...)."}}},"PaymentMethod":{"type":"string","enum":["cash","card","check","voucher","gift_card","points","mobile","transfer","credit","other"]},"PresentedCoupon":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","minLength":1,"maxLength":64,"description":"Coupon code as scanned/typed (EAN-13, QR payload or short code)."},"source":{"type":"string","enum":["scanned","typed","app","auto"],"default":"scanned"}}},"Options":{"type":"object","additionalProperties":false,"description":"Per-call behaviour switches. Defaults come from workspace settings.","properties":{"coherenceCheck":{"type":"boolean","description":"Emit a warning when sum(lines) != totals.grossCents. Never blocks."},"explain":{"type":"boolean","default":true,"description":"Include rejected offers with reasons in the result."},"offerIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Simulator only: restrict evaluation to these offers (draft versions allowed when called through the hub simulator)."},"pointsBurn":{"type":"object","additionalProperties":false,"description":"Ask the engine to burn counter units as part of this ticket (alternative to a prior /points/reserve).","required":["counterTypeCode","amount"],"properties":{"counterTypeCode":{"type":"string"},"amount":{"type":"integer","minimum":1},"reservationId":{"type":"string","format":"uuid","description":"Existing reservation to confirm at commit."}}}}}}},"Engine_Transaction":{"type":"object","required":["transactionId","ticketKey","kind","state","createdAt","ticket","result"],"properties":{"transactionId":{"type":"string","format":"uuid"},"ticketKey":{"type":"string"},"kind":{"type":"string","enum":["sale","refund"]},"state":{"type":"string","enum":["pending","committed","cancelled","refunded","partially_refunded","expired"]},"parentTransactionId":{"type":["string","null"],"format":"uuid"},"adapter":{"type":"string","enum":["native","wsone"]},"apiKeyId":{"type":"string"},"customerId":{"type":["string","null"],"format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"committedAt":{"type":["string","null"],"format":"date-time"},"cancelledAt":{"type":["string","null"],"format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time","description":"Pending TTL."},"ticket":{"$ref":"#/components/schemas/Ticket"},"result":{"$ref":"#/components/schemas/EvaluationResult"},"refunds":{"type":"array","items":{"type":"object","properties":{"transactionId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"refundedCents":{"type":"integer"}}}}}}}}}