{"openapi":"3.0.0","info":{"contact":{"name":"ScrumDesk s r.o."},"title":"FlowOS Experiments Module API","version":"1.0.0","description":"FlowTwin Experiment Hub — experiment contracts, candidate expansion, comparison/decision records, and S-4.5 POST /search (filter/sort/pagination). Secured routes require Bearer JWT and organization context."},"tags":[{"name":"Health","description":"Liveness probe"},{"name":"Seed","description":"Platform Admin Seed"},{"name":"Experiments","description":"Experiment CRUD and candidate plans"}],"paths":{"/health":{"get":{"summary":"Liveness probe","description":"Unauthenticated. Returns ok when the process is up; does not check Postgres or Redis.","tags":["Health"],"responses":{"200":{"description":"Service is up","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[],"security":[]}},"/admin/status":{"get":{"summary":"Admin status","description":"Platform module status. May report degraded when the database ping fails.","tags":["Admin"],"responses":{"200":{"description":"ok or degraded","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[],"security":[]}},"/admin/verify":{"post":{"summary":"Admin verify","description":"Platform verification handshake used by registry/health tooling.","tags":["Admin"],"responses":{"200":{"description":"Acknowledged","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"service":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}},"parameters":[],"security":[]}},"/seed":{"post":{"summary":"Platform Admin Seed","description":"Idempotent per-organization seed. Body is the platform Seed payload (organizationUuids map).","tags":["Seed"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSeedRequest"}}}},"responses":{"200":{"description":"Seed completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSeedResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}},"parameters":[],"security":[]}},"/seed-cleanup":{"post":{"summary":"Seed cleanup (no-op)","description":"Acknowledges platform seed-cleanup. FlowTwin seeds are upsert-based and do not delete tenant data.","tags":["Seed"],"responses":{"200":{"description":"Acknowledged","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}},"parameters":[],"security":[]}},"/create":{"post":{"summary":"Create experiment and expand candidates","description":"Validates the PRD §D.1 contract, resolves AUTO→GRID (≤512 discrete combos) or LHS, expands a deterministic candidate plan. Returns domainSizeWarning when search space > 10⁴. Requires experiments:write.","operationId":"createExperiment","tags":["Experiments"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentContract"}}}},"responses":{"201":{"description":"Created with candidate plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentCreated"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Contract or expansion failed"}},"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"}]}},"/list":{"get":{"summary":"List experiments","description":"Tenant-scoped, newest first.","operationId":"listExperiments","tags":["Experiments"],"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"},"description":"Page after this item UUID"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Paged summaries","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ExperimentSummary"}},"nextCursor":{"type":"string","format":"uuid"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/search":{"post":{"summary":"Search experiments (S-4.5)","description":"Org-scoped filter/sort/pagination over experiments. Soft-deleted rows are excluded. Tenant scope comes only from validated JWT/org context — never from the request body.\n\n**Date bounds:** `createdAfter` is inclusive (`createdAt >=`), `createdBefore` is exclusive (`createdAt <`).\n\n**Candidate-summary rule:** `feasible` and `objectives` filters match an experiment when **at least one DONE candidate** satisfies every requested bound (PENDING/RUNNING/FAILED/SKIPPED ignored). Objective KPI values use metric **mean** (same as `metricValue`). Response `objectives` summarize the best-ranked DONE feasible candidate (else best DONE).\n\n**Aliases:** story fields `algorithmType`/`id`/`label` map to `resolvedStrategy`/`uuid`/`name`. Unknown sort/projection fields → 422 `E_VALIDATION`.","operationId":"searchExperiments","tags":["Experiments"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentSearchRequest"}}}},"responses":{"200":{"description":"Matching experiments with pagination metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentSearchResponse"}}}},"401":{"description":"Missing or invalid auth/org context"},"403":{"description":"Missing experiments:read scope"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Unexpected search failure"}},"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"}]}},"/detail/{uuid}":{"get":{"summary":"Experiment detail with candidates","operationId":"getExperiment","tags":["Experiments"],"responses":{"200":{"description":"Experiment plus candidate plan and metrics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"},{"in":"path","name":"uuid","required":true,"schema":{"type":"string","format":"uuid"}}]},"delete":{"summary":"Soft-delete experiment","tags":["Experiments"],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"},{"in":"path","name":"uuid","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/detail/{uuid}/start":{"post":{"summary":"Start or resume evaluation","description":"Evaluates PENDING candidates against the sim engine. Resumes CANCELLED/FAILED (re-queues SKIPPED). 202; poll GET detail. 409 if already RUNNING or COMPLETED.","tags":["Experiments"],"responses":{"202":{"description":"Started","content":{"application/json":{"schema":{"type":"object","properties":{"started":{"type":"boolean"},"uuid":{"type":"string","format":"uuid"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Already running or completed"},"422":{"$ref":"#/components/responses/ValidationError"}},"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"},{"in":"path","name":"uuid","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/detail/{uuid}/cancel":{"post":{"summary":"Cancel a running experiment","description":"Remaining candidates are marked SKIPPED. Requires experiments:write.","tags":["Experiments"],"responses":{"200":{"description":"Cancellation recorded","content":{"application/json":{"schema":{"type":"object","properties":{"cancelled":{"type":"boolean"},"uuid":{"type":"string","format":"uuid"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}},"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"},{"in":"path","name":"uuid","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/detail/{uuid}/decision":{"post":{"summary":"Create a decision record","description":"For a DONE candidate: runs the baseline for comparison, KPI deltas (CI-overlap significance), and change backlog. Requires experiments:write.","tags":["Experiments"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionRequest"}}}},"responses":{"201":{"description":"Decision record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionRecord"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Candidate not evaluated yet"},"502":{"description":"Baseline comparison failed"}},"parameters":[{"$ref":"#/components/parameters/OrganizationUuidHeader"},{"in":"path","name":"uuid","required":true,"schema":{"type":"string","format":"uuid"}}]}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"FlowOS access token from /api/auth/login (+ org selection when required)."}},"parameters":{"OrganizationUuidHeader":{"in":"header","name":"X-Organization-Uuid","schema":{"type":"string","format":"uuid"},"required":false,"description":"Active organization UUID (gateway forwards from session). When sent, must match the organization bound to the JWT."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Validation or diagnostic details","additionalProperties":true},"message":{"type":"string","description":"Human-readable detail"}},"required":["error"]},"ApiErrorEnvelope":{"type":"object","description":"FlowTwin and most vendor modules return `{ error: { code, message } }`.","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine code (E_VALIDATION, E_FORBIDDEN, E_NOT_FOUND, …)"},"message":{"type":"string"},"details":{"description":"Optional Zod flatten or diagnostic payload"}}}}},"HealthStatus":{"type":"object","required":["status"],"properties":{"status":{"type":"string","example":"ok"},"service":{"type":"string"},"module":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"UuidCreated":{"type":"object","required":["uuid"],"properties":{"uuid":{"type":"string","format":"uuid"}}},"PlatformSeedRequest":{"type":"object","description":"Platform Admin Seed body. Provide organizationUuids map and/or organizationUuid.","properties":{"organizations":{"type":"array","items":{"type":"string"}},"organizationUuids":{"type":"object","additionalProperties":{"type":"string","format":"uuid"}},"organizationUuid":{"type":"string","format":"uuid"}}},"PlatformSeedResult":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"recordsCreated":{"type":"integer"},"results":{"type":"array","items":{"type":"object","additionalProperties":true}},"organizationUuids":{"type":"array","items":{"type":"string","format":"uuid"}}}},"Objective":{"type":"object","required":["metric","direction"],"properties":{"metric":{"type":"string"},"direction":{"type":"string","enum":["MIN","MAX"]}}},"Constraint":{"type":"object","required":["metric","op","value"],"properties":{"metric":{"type":"string"},"op":{"type":"string","enum":["<=",">=","<",">","=="]},"value":{"type":"number"},"hard":{"type":"boolean","default":true}}},"DecisionVariable":{"type":"object","required":["target","domain"],"properties":{"target":{"type":"string","description":"Attribute path, e.g. step.assemble.dataBox.operators or global.batchSize"},"domain":{"oneOf":[{"type":"object","required":["min","max"],"properties":{"min":{"type":"number"},"max":{"type":"number"},"step":{"type":"number","exclusiveMinimum":0}}},{"type":"object","required":["options"],"properties":{"options":{"type":"array","minItems":1,"items":{"oneOf":[{"type":"string"},{"type":"number"}]}}}}]}}},"ExperimentContract":{"type":"object","required":["name","baselineScenarioUuid","objectives","decisionVariables"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"baselineScenarioUuid":{"type":"string","format":"uuid"},"objectives":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/Objective"}},"constraints":{"type":"array","maxItems":12,"items":{"$ref":"#/components/schemas/Constraint"}},"decisionVariables":{"type":"array","minItems":1,"maxItems":20,"items":{"$ref":"#/components/schemas/DecisionVariable"}},"searchStrategy":{"type":"string","enum":["AUTO","GRID","LHS","NSGA2","BAYESIAN"],"default":"AUTO"},"simConfig":{"type":"object","properties":{"replications":{"type":"integer","minimum":1,"maximum":1000,"default":100},"horizonDays":{"type":"number","default":30},"warmupDays":{"type":"number","default":0},"masterSeed":{"type":"integer","default":42}}},"budget":{"type":"object","properties":{"maxCandidates":{"type":"integer","minimum":1,"maximum":10000,"default":400},"maxWallClockMin":{"type":"number","default":60}}}}},"ExperimentCreated":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"status":{"type":"string"},"domainSizeWarning":{"type":"boolean"}}},"ExperimentSummary":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string"},"baselineScenarioUuid":{"type":"string","format":"uuid"}}},"ExperimentDetail":{"type":"object","description":"Experiment row plus candidates (ordinal, configValues, status, metrics, feasible, paretoFront).","additionalProperties":true},"DecisionRequest":{"type":"object","required":["candidateUuid"],"properties":{"candidateUuid":{"type":"string","format":"uuid"},"rationale":{"type":"string","maxLength":4000}}},"DecisionRecord":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"kpiDeltas":{"type":"object","additionalProperties":true},"changeBacklog":{"type":"array","items":{"type":"object"}}}},"ExperimentSearchRequest":{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object","additionalProperties":false,"properties":{"status":{"type":"array","minItems":1,"items":{"type":"string","enum":["DRAFT","RUNNING","PAUSED","COMPLETED","CANCELLED","FAILED"]},"description":"Prisma ExperimentStatus values (lowercase accepted and normalized)"},"resolvedStrategy":{"type":"array","minItems":1,"items":{"type":"string","enum":["GRID","LHS","NSGA2","BAYESIAN"]}},"algorithmType":{"type":"array","minItems":1,"items":{"type":"string","enum":["GRID","LHS","NSGA2","BAYESIAN"]},"description":"Story alias merged with resolvedStrategy"},"createdAfter":{"type":"string","format":"date-time","description":"Inclusive lower bound (createdAt >=)"},"createdBefore":{"type":"string","format":"date-time","description":"Exclusive upper bound (createdAt <)"},"feasible":{"type":"boolean","description":"At least one DONE candidate with this feasible flag"},"objectives":{"type":"object","additionalProperties":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"description":"KPI key → min/max on candidate metric means"},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Case-insensitive partial match on experiment name"}}},"sort":{"type":"object","additionalProperties":false,"properties":{"field":{"type":"string","description":"Allowlisted: createdAt, updatedAt, name, status, resolvedStrategy, or objectives.<kpi>"},"direction":{"type":"string","enum":["asc","desc"],"default":"desc"}}},"pagination":{"type":"object","additionalProperties":false,"properties":{"page":{"type":"integer","minimum":1,"default":1},"limit":{"type":"integer","minimum":1,"maximum":100,"default":20}}},"fields":{"type":"array","items":{"type":"string","enum":["uuid","id","name","label","status","resolvedStrategy","algorithmType","baselineScenarioUuid","searchSpaceSize","createdAt","updatedAt","candidateCount","objectives","constraints","duration"]},"description":"Optional projection; uuid is always included"}}},"ExperimentSearchItem":{"type":"object","required":["uuid"],"properties":{"uuid":{"type":"string","format":"uuid"},"id":{"type":"string","format":"uuid","description":"Alias of uuid"},"name":{"type":"string"},"label":{"type":"string","description":"Alias of name"},"status":{"type":"string"},"resolvedStrategy":{"type":"string"},"algorithmType":{"type":"string","description":"Alias of resolvedStrategy"},"baselineScenarioUuid":{"type":"string","format":"uuid"},"searchSpaceSize":{"type":"number"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"candidateCount":{"type":"integer"},"objectives":{"type":"object","additionalProperties":{"type":"number"}},"constraints":{"type":"array","items":{"type":"object"}},"duration":{"type":"integer","nullable":true,"description":"Approximate wall duration in seconds (updatedAt - createdAt)"}}},"ExperimentSearchResponse":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExperimentSearchItem"}},"pagination":{"type":"object","required":["total","page","limit","pages"],"properties":{"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"pages":{"type":"integer"}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid authentication / organization context","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/ApiErrorEnvelope"}]}}}},"Forbidden":{"description":"Authenticated but missing module scope or organization mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorEnvelope"}}}},"ValidationError":{"description":"Request validation failed (E_VALIDATION)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"E_VALIDATION"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true}}}}}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/ApiErrorEnvelope"}]}}}}}},"security":[{"bearerAuth":[]}],"servers":[{"url":"/api/experiments","description":"Current host (gateway — use for Swagger Try it out)"},{"url":"https://flowos.scrumdesk.com/api/experiments","description":"Production absolute URL (Postman / third parties)"},{"url":"http://localhost:3016/api/experiments","description":"Local module backend (direct on module port)"}]}