openapi: 3.0.3
info:
  title: PlanFinder Generator API
  version: 1.3.0
  description: Generate completed residential plans with your choice of 1–32 attempts per job. Constraints are checked
    against clear room geometry and actual placed doors. The integration trial includes 2000 reserved attempts for
    14 days, whichever ends first; no card or automatic renewal.
servers:
- url: http://127.0.0.1:4321
  description: Local website proxy for the current update.
- url: http://127.0.0.1:4181
  description: Local API handler.
- url: https://2ufepzbwb5.execute-api.eu-central-1.amazonaws.com
  description: Hosted API; new plan routes require this update to be deployed.
paths:
  /v1/trials:
    post:
      operationId: requestIntegrationTrial
      summary: Email a single-use verification link to get started without a sales call.
      description: Requests are limited to 2 KB and rate limited by email, IP and globally. No trial starts until
        the email link is verified. Requires configured SES delivery on the hosted API.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - email
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 254
      responses:
        '202':
          description: Verification email requested. The link expires in 10 minutes and works once.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  message:
                    type: string
                  delivery:
                    type: string
                    enum:
                    - email
        '400':
          $ref: '#/components/responses/Error'
        '413':
          $ref: '#/components/responses/Error'
        '429':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/Error'
  /v1/trials/verify:
    post:
      operationId: createIntegrationTrialKey
      summary: Verify the email link and receive an API key once.
      description: The trial starts on first verification, lasting at most 14 days or 2000 attempts. Later keys
        for the same verified email share the original expiry and allowance. Used links cannot be replayed to recover
        a key; request a fresh email link instead.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - token
              properties:
                token:
                  type: string
                  pattern: ^[A-Za-z0-9_-]{43}$
      responses:
        '201':
          description: Key created. Store it securely now; its plaintext cannot be recovered. Response uses Cache-Control
            no-store.
          content:
            application/json:
              schema:
                type: object
                required:
                - ok
                - apiKey
                - usage
                - maxAttemptsPerRequest
                properties:
                  ok:
                    type: boolean
                  apiKey:
                    type: string
                    readOnly: true
                  usage:
                    $ref: '#/components/schemas/Usage'
                  maxAttemptsPerRequest:
                    type: integer
                    enum:
                    - 32
                  accountToken: &id004
                    type: string
                    readOnly: true
                    description: 12-hour account-management bearer token. Keep separate from generation keys; never
                      use it for /v1/plans.
        '400':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
        '410':
          $ref: '#/components/responses/Error'
        '413':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/Error'
  /v1/demo/slicings:
    post:
      operationId: createDemoSlicings
      summary: Public, rate-limited generation sandbox used by the PlanFinder website.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DemoSlicingRequest'
      responses:
        '200':
          description: One or more unique demo slicings were generated or returned from the demo cache.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SlicingResponse'
        '400':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
        '409':
          $ref: '#/components/responses/Error'
        '413':
          $ref: '#/components/responses/Error'
        '422':
          $ref: '#/components/responses/Error'
        '429':
          $ref: '#/components/responses/Error'
        '500':
          $ref: '#/components/responses/Error'
        '502':
          $ref: '#/components/responses/Error'
  /v1/slicings:
    post:
      operationId: createSlicings
      summary: Generate unique residential room layouts.
      description: 'Request bodies are limited to 64 KB. Each requested attempt counts toward usage, including duplicates
        and attempts without valid layouts. Reuse an idempotency key with the same body to replay a completed result
        for seven days. Compatibility route: paid requests of 1–8 attempts without settings/requirements return
        raw slicings synchronously. Trial requests, larger batches (up to 32), or requests with constraints use
        the completed-plan job pipeline and return 202. New integrations should use /v1/plans.'
      security:
      - bearerAuth: []
      parameters:
      - name: Idempotency-Key
        in: header
        required: true
        schema:
          type: string
          maxLength: 200
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanRequest'
      responses:
        '200':
          description: One or more unique slicings were generated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SlicingResponse'
        '400':
          $ref: '#/components/responses/Error'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
        '409':
          $ref: '#/components/responses/Error'
        '413':
          $ref: '#/components/responses/Error'
        '422':
          $ref: '#/components/responses/Error'
        '429':
          $ref: '#/components/responses/Error'
        '500':
          $ref: '#/components/responses/Error'
        '502':
          $ref: '#/components/responses/Error'
        '202': &id001
          description: Generation job; keep polling statusUrl until succeeded or failed. Paid jobs persist for seven
            days; public demo jobs for 24 hours.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanJob'
  /v1/usage:
    get:
      operationId: getUsage
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Current inference usage for the authenticated customer.
          content:
            application/json:
              schema:
                type: object
                required:
                - ok
                - requestId
                - usage
                properties:
                  ok:
                    type: boolean
                  requestId:
                    type: string
                  usage:
                    $ref: '#/components/schemas/Usage'
        '401':
          $ref: '#/components/responses/Error'
  /health:
    get:
      operationId: health
      responses:
        '200':
          description: Process health; does not check downstream AWS services.
  /v1/plans:
    post:
      operationId: createPlan
      summary: Queue 1–32 attempts and complete plans matching the brief.
      description: Returns a job immediately. Constraints filter completed candidates, so a valid brief can yield
        a failed job with NO_MATCHING_PLANS (422); this is not a guarantee of a feasible design. Attempts are reserved
        on acceptance, including duplicate or rejected results. Invalid submissions and idempotent replays do not
        consume attempts. No batch cancellation is offered after acceptance.
      security:
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanRequest'
      responses:
        '202': *id001
        '400': &id002
          $ref: '#/components/responses/Error'
        '401': *id002
        '403': *id002
        '409': *id002
        '413': *id002
        '429': *id002
        '500': *id002
        '503': *id002
      parameters:
      - name: Idempotency-Key
        in: header
        required: true
        schema:
          type: string
          minLength: 1
          maxLength: 200
  /v1/jobs/{jobId}:
    get:
      operationId: getPlanJob
      security:
      - bearerAuth: []
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200': *id001
        '401': *id002
        '404': *id002
  /v1/demo/plans:
    post:
      operationId: createDemoPlan
      summary: Queue 1–32 attempts and complete plans matching the brief.
      description: 'Returns a job immediately. Constraints filter completed candidates, so a valid brief can yield
        a failed job with NO_MATCHING_PLANS (422); this is not a guarantee of a feasible design. Attempts are reserved
        on acceptance, including duplicate or rejected results. Invalid submissions and idempotent replays do not
        consume attempts. No batch cancellation is offered after acceptance. Public job URLs are unguessable capability
        links: anyone with the link can read that demo job. Identical public briefs reuse the existing job for 24
        hours.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DemoPlanRequest'
      responses:
        '202': *id001
        '400': *id002
        '401': *id002
        '403': *id002
        '409': *id002
        '413': *id002
        '429': *id002
        '500': *id002
        '503': *id002
  /v1/demo/jobs/{jobId}:
    get:
      operationId: getDemoPlanJob
      security: []
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200': *id001
        '401': *id002
        '404': *id002
  /v1/validate:
    post:
      operationId: validatePlanInput
      summary: Validate a complete brief without using attempts.
      security: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InputValidation'
        '400': &id003
          $ref: '#/components/responses/Error'
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      description: Runs the same input checks as generation, including supported countries, simple orthogonal outlines,
        units, facades, entrance, settings and requirement syntax. No inference, storage or attempt reservation.
        Valid input does not guarantee a feasible layout.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanRequest'
  /v1/account/login:
    post:
      operationId: requestAccountSignIn
      summary: Email a single-use account sign-in link.
      security: []
      responses:
        '202':
          description: Operation completed.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                properties:
                  ok:
                    type: boolean
                  message:
                    type: string
                  delivery:
                    type: string
                    enum:
                    - email
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      description: Rate limited with trial signup; 10-minute hashed tokens in URL fragments. Never resets a trial
        or grants paid access.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AccountEmail'
  /v1/account/verify:
    post:
      operationId: verifyAccountSignIn
      summary: Exchange a single-use email token for an account session.
      security: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountSession'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerificationToken'
  /v1/account:
    get:
      operationId: getApiAccount
      summary: Read owned keys, trial, billing status and shared allowance.
      security:
      - accountAuth: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Account'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
  /v1/account/logout:
    post:
      operationId: signOutApiAccount
      summary: Revoke this account session.
      security:
      - accountAuth: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OperationResult'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties: {}
  /v1/account/keys:
    post:
      operationId: createAccountApiKey
      summary: Create a generation key, returned once.
      security:
      - accountAuth: []
      responses:
        '201':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewAccountKey'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      description: Requires an active trial or paid subscription. At most 20 active keys; all share the account
        allowance. Only salted hashes are stored.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 64
              required:
              - name
  /v1/account/keys/{keyId}:
    delete:
      operationId: revokeAccountApiKey
      summary: Revoke a generation key owned by this account.
      security:
      - accountAuth: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OperationResult'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      parameters:
      - in: path
        name: keyId
        required: true
        schema:
          type: string
  /v1/account/checkout:
    post:
      operationId: createMonthlyCheckout
      summary: Open a hosted monthly subscription checkout.
      security:
      - accountAuth: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingRedirect'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      description: Server selects the verified EUR monthly price. Repeated requests reuse the pending session. No
        access is granted by the redirect; signed lifecycle events and a current paid invoice activate the billing-period
        allowance. Existing subscriptions use the billing portal.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                plan:
                  type: string
                  enum:
                  - starter
                  - growth
                  - scale
              required:
              - plan
  /v1/account/portal:
    post:
      operationId: createBillingPortal
      summary: Manage payment method, invoices, plan changes and cancellation.
      security:
      - accountAuth: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingRedirect'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      description: Uses only the authenticated account customer. Client customer IDs and redirect URLs are rejected.
        Downgrades and cancellation apply at period end; paid upgrades are prorated. Reserved usage persists for
        a plan change within the same billing period.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties: {}
  /v1/billing/webhook:
    post:
      operationId: stripeBillingNotification
      summary: Receive signed Stripe subscription and invoice events.
      security: []
      responses:
        '200':
          description: Operation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OperationResult'
        '400': *id003
        '401': *id003
        '403': *id003
        '409': *id003
        '429': *id003
        '503': *id003
      description: Stripe-only endpoint requiring the raw-body Stripe-Signature header. Verifies timestamp, signature,
        sandbox/live mode, customer ownership, configured price and current paid invoice. Deduplicates events; fetches
        current provider state so delayed events cannot reset usage or overwrite newer state.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      parameters:
      - in: header
        name: Stripe-Signature
        required: true
        schema:
          type: string
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
    accountAuth:
      type: http
      scheme: bearer
      bearerFormat: pf_account_*
      description: Account-management token issued after verified email sign-in. Distinct from server-side generation
        API keys.
  responses:
    Error:
      description: Request error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    Point:
      type: array
      minItems: 2
      maxItems: 2
      items:
        type: number
    Polyline:
      type: array
      minItems: 2
      items:
        $ref: '#/components/schemas/Point'
    RequiredRoom:
      type: object
      additionalProperties: false
      required:
      - function
      - location
      properties:
        function:
          type: string
          enum:
          - Circulation
          - Living
          - Bed
          - Bath
          - WC
          - Storage
          - Kitchen
          - WalkInCloset
          - FuseBox
          - Shaft
        location:
          $ref: '#/components/schemas/Point'
    Geometry:
      type: object
      additionalProperties: false
      required:
      - units
      - boundary
      - facades
      - entrance
      properties:
        units:
          type: string
          enum:
          - mm
          - cm
          - m
          - in
          - ft
        boundary:
          type: array
          minItems: 4
          items:
            $ref: '#/components/schemas/Point'
        facades:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Polyline'
        entrance:
          $ref: '#/components/schemas/Point'
        requiredRooms:
          type: array
          default: []
          items:
            $ref: '#/components/schemas/RequiredRoom'
    Program:
      type: object
      additionalProperties: false
      required:
      - beds
      - baths
      - wc
      - storage
      - kitchen
      - walkInCloset
      - fuseBox
      properties:
        beds:
          type: integer
          minimum: 0
          maximum: 4
        baths:
          type: integer
          minimum: 1
          maximum: 4
        wc:
          type: integer
          minimum: 0
          maximum: 1
        storage:
          type: integer
          minimum: 0
          maximum: 1
        kitchen:
          type: integer
          minimum: 0
          maximum: 1
        walkInCloset:
          type: integer
          minimum: 0
          maximum: 1
        fuseBox:
          type: integer
          minimum: 0
          maximum: 1
        shaft:
          type: integer
          minimum: 0
          maximum: 1
          default: 0
    SlicingRequest:
      type: object
      additionalProperties: false
      required:
      - geometry
      - program
      - country
      properties:
        geometry:
          $ref: '#/components/schemas/Geometry'
        program:
          $ref: '#/components/schemas/Program'
        country:
          $ref: '#/components/schemas/Country'
        options:
          type: object
          additionalProperties: false
          properties:
            candidates:
              type: integer
              minimum: 1
              maximum: 8
              description: Number of generation attempts, not a guaranteed count of returned variants. Choose the
                count for each request; the maximum is a limit, not a recommended batch size. Duplicate and non-matching
                results count. This compatibility schema describes explicit batches of up to 8 attempts; omitting
                the count defaults to a completed 16-attempt job.
            adjustEntrance:
              type: boolean
              default: true
    DemoSlicingRequest:
      type: object
      additionalProperties: false
      required:
      - geometry
      - program
      - country
      properties:
        geometry:
          $ref: '#/components/schemas/Geometry'
        program:
          $ref: '#/components/schemas/Program'
        country:
          $ref: '#/components/schemas/Country'
        options:
          type: object
          additionalProperties: false
          properties:
            candidates:
              type: integer
              minimum: 1
              maximum: 16
              default: 16
              description: Number of generation attempts, not a guaranteed count of returned variants. Choose the
                count for each request; the maximum is a limit, not a recommended batch size. Duplicate and non-matching
                results count.
            adjustEntrance:
              type: boolean
              default: true
        turnstileToken:
          type: string
          description: Required only when Turnstile protection is enabled on the deployed demo.
    Room:
      type: object
      required:
      - function
      - polygon
      properties:
        function:
          type: string
        polygon:
          type: array
          minItems: 3
          items:
            $ref: '#/components/schemas/Point'
    Candidate:
      type: object
      required:
      - seed
      - rooms
      properties:
        seed:
          type: integer
        rooms:
          type: array
          items:
            $ref: '#/components/schemas/Room'
        entrance:
          $ref: '#/components/schemas/Point'
    Usage:
      type: object
      properties:
        usage:
          type: integer
        limit:
          type: integer
        remaining:
          type: integer
        period:
          type: string
        resetDate:
          type: string
          format: date-time
        tier:
          type: string
          enum:
          - PLUGIN
          - API
          - TRIAL
          - SUBSCRIPTION
        expiresAt:
          type: string
          format: date-time
        expired:
          type: boolean
        autoRenews:
          type: boolean
        plan:
          type: string
          enum:
          - starter
          - growth
          - scale
        active:
          type: boolean
    SlicingResponse:
      type: object
      required:
      - ok
      - requestId
      - modelVersion
      - units
      - requestedAttempts
      - uniqueCandidates
      - candidates
      properties:
        ok:
          type: boolean
        requestId:
          type: string
        modelVersion:
          type: string
        units:
          type: string
        requestedAttempts:
          type: integer
        uniqueCandidates:
          type: integer
        candidates:
          type: array
          items:
            $ref: '#/components/schemas/Candidate'
        usage:
          $ref: '#/components/schemas/Usage'
    ErrorResponse:
      type: object
      required:
      - ok
      - requestId
      - error
      properties:
        ok:
          type: boolean
          enum:
          - false
        requestId:
          type: string
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: string
            message:
              type: string
            details: {}
    RoomRequirement:
      type: object
      additionalProperties: false
      description: Applies to every room of this function. With minWidth alone, the shorter side of the largest-area
        clear inscribed rectangle must meet that width. Add minDepth to require a clear inscribed rectangle fitting
        both dimensions in either orientation; minDepth requires minWidth. Clear area excludes half of each surrounding
        wall. Facade contact is geometric, and does not assert a daylight or regulatory calculation.
      properties:
        minWidth:
          type: number
          exclusiveMinimum: true
          minimum: 0
          description: Minimum shorter side of the largest-area clear rectangle when used alone. With minDepth,
            a clear rectangle must fit both dimensions in either orientation. Linear values use geometry.units.
        minDepth:
          type: number
          exclusiveMinimum: true
          minimum: 0
          description: Optional minimum clear rectangle depth; requires minWidth. Linear values use geometry.units.
        minAreaM2:
          type: number
          minimum: 0.001
          maximum: 250
        facade:
          type: string
          enum:
          - any
          - required
          - forbidden
          default: any
        minFacadeLength:
          type: number
          exclusiveMinimum: true
          minimum: 0
          description: Minimum total contact with the supplied facade polylines. Linear values use geometry.units.
    PlanRequest:
      type: object
      additionalProperties: false
      required:
      - geometry
      - program
      - country
      properties:
        geometry:
          $ref: '#/components/schemas/Geometry'
        program:
          $ref: '#/components/schemas/Program'
        country:
          $ref: '#/components/schemas/Country'
        options:
          type: object
          additionalProperties: false
          properties:
            candidates:
              type: integer
              minimum: 1
              maximum: 32
              default: 16
              description: Number of generation attempts, not a guaranteed count of returned variants. Choose the
                count for each request; the maximum is a limit, not a recommended batch size. Duplicate and non-matching
                results count.
            adjustEntrance:
              type: boolean
              default: true
              description: Allow the entrance centre to move along its original straight wall, up to maxEntranceShift
                in either direction. The original point is kept when it fits. With false, the entrance stays fixed.
                Layouts whose opening or inward swing intersects walls are rejected in either mode.
            maxEntranceShift:
              type: number
              minimum: 0
              description: Maximum entrance movement in either direction, in geometry.units. Defaults to 0.5 m (500
                mm); allowed range 0–2 m. Ignored when adjustEntrance is false.
        settings:
          type: object
          additionalProperties: false
          properties:
            wallThicknesses:
              type: object
              additionalProperties: false
              properties:
                exterior:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Exterior wall thickness, 0.1–0.6 m; default 0.2 m. Linear values use geometry.units.
                interior:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Interior wall thickness, 0.05–0.4 m; default 0.1 m. Linear values use geometry.units.
            doorWidths:
              type: object
              additionalProperties: false
              description: 'Defaults: interior 0.9 m, entrance 1 m, service 0.7 m. The engine places doors using
                these nominal leaf sizes.'
              properties:
                interior:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Requested nominal door leaf width (0.5–2 m). Linear values use geometry.units.
                entrance:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Requested nominal door leaf width (0.5–2 m). Linear values use geometry.units.
                service:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Requested nominal door leaf width (0.5–2 m). Linear values use geometry.units.
        requirements:
          type: object
          additionalProperties: false
          properties:
            rooms:
              type: object
              additionalProperties: false
              properties:
                Living:
                  $ref: '#/components/schemas/RoomRequirement'
                Circulation:
                  $ref: '#/components/schemas/RoomRequirement'
                Bed:
                  $ref: '#/components/schemas/RoomRequirement'
                Bath:
                  $ref: '#/components/schemas/RoomRequirement'
                WC:
                  $ref: '#/components/schemas/RoomRequirement'
                Storage:
                  $ref: '#/components/schemas/RoomRequirement'
                Kitchen:
                  $ref: '#/components/schemas/RoomRequirement'
                WalkInCloset:
                  $ref: '#/components/schemas/RoomRequirement'
                FuseBox:
                  $ref: '#/components/schemas/RoomRequirement'
                Shaft:
                  $ref: '#/components/schemas/RoomRequirement'
            connections:
              type: array
              maxItems: 64
              items:
                type: object
                additionalProperties: false
                required:
                - from
                - to
                description: A required rule requires every matching from-room to have a direct placed door to at
                  least one matching to-room. A forbidden rule permits no direct door between the functions. Adjacency
                  alone is insufficient.
                properties:
                  from:
                    type: string
                    enum:
                    - Living
                    - Circulation
                    - Bed
                    - Bath
                    - WC
                    - Storage
                    - Kitchen
                    - WalkInCloset
                    - FuseBox
                    - Shaft
                  to:
                    type: string
                    enum:
                    - Living
                    - Circulation
                    - Bed
                    - Bath
                    - WC
                    - Storage
                    - Kitchen
                    - WalkInCloset
                    - FuseBox
                    - Shaft
                  relation:
                    type: string
                    enum:
                    - required
                    - forbidden
                    default: required
                  minDoorWidth:
                    type: number
                    exclusiveMinimum: true
                    minimum: 0
                    description: Minimum nominal width of the connecting door; required rules only. Linear values
                      use geometry.units.
            requireConnected:
              type: boolean
              default: true
              description: All accessible rooms must have a placed-door path to the entrance. Closed shafts are
                exempt.
    DemoPlanRequest:
      type: object
      additionalProperties: false
      required:
      - geometry
      - program
      - country
      properties:
        geometry:
          $ref: '#/components/schemas/Geometry'
        program:
          $ref: '#/components/schemas/Program'
        country:
          $ref: '#/components/schemas/Country'
        options:
          type: object
          additionalProperties: false
          properties:
            candidates:
              type: integer
              minimum: 1
              maximum: 32
              default: 16
              description: Number of generation attempts, not a guaranteed count of returned variants. Choose the
                count for each request; the maximum is a limit, not a recommended batch size. Duplicate and non-matching
                results count.
            adjustEntrance:
              type: boolean
              default: true
              description: Allow the entrance centre to move along its original straight wall, up to maxEntranceShift
                in either direction. The original point is kept when it fits. With false, the entrance stays fixed.
                Layouts whose opening or inward swing intersects walls are rejected in either mode.
            maxEntranceShift:
              type: number
              minimum: 0
              description: Maximum entrance movement in either direction, in geometry.units. Defaults to 0.5 m (500
                mm); allowed range 0–2 m. Ignored when adjustEntrance is false.
        settings:
          type: object
          additionalProperties: false
          properties:
            wallThicknesses:
              type: object
              additionalProperties: false
              properties:
                exterior:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Exterior wall thickness, 0.1–0.6 m; default 0.2 m. Linear values use geometry.units.
                interior:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Interior wall thickness, 0.05–0.4 m; default 0.1 m. Linear values use geometry.units.
            doorWidths:
              type: object
              additionalProperties: false
              description: 'Defaults: interior 0.9 m, entrance 1 m, service 0.7 m. The engine places doors using
                these nominal leaf sizes.'
              properties:
                interior:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Requested nominal door leaf width (0.5–2 m). Linear values use geometry.units.
                entrance:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Requested nominal door leaf width (0.5–2 m). Linear values use geometry.units.
                service:
                  type: number
                  exclusiveMinimum: true
                  minimum: 0
                  description: Requested nominal door leaf width (0.5–2 m). Linear values use geometry.units.
        requirements:
          type: object
          additionalProperties: false
          properties:
            rooms:
              type: object
              additionalProperties: false
              properties:
                Living:
                  $ref: '#/components/schemas/RoomRequirement'
                Circulation:
                  $ref: '#/components/schemas/RoomRequirement'
                Bed:
                  $ref: '#/components/schemas/RoomRequirement'
                Bath:
                  $ref: '#/components/schemas/RoomRequirement'
                WC:
                  $ref: '#/components/schemas/RoomRequirement'
                Storage:
                  $ref: '#/components/schemas/RoomRequirement'
                Kitchen:
                  $ref: '#/components/schemas/RoomRequirement'
                WalkInCloset:
                  $ref: '#/components/schemas/RoomRequirement'
                FuseBox:
                  $ref: '#/components/schemas/RoomRequirement'
                Shaft:
                  $ref: '#/components/schemas/RoomRequirement'
            connections:
              type: array
              maxItems: 64
              items:
                type: object
                additionalProperties: false
                required:
                - from
                - to
                description: A required rule requires every matching from-room to have a direct placed door to at
                  least one matching to-room. A forbidden rule permits no direct door between the functions. Adjacency
                  alone is insufficient.
                properties:
                  from:
                    type: string
                    enum:
                    - Living
                    - Circulation
                    - Bed
                    - Bath
                    - WC
                    - Storage
                    - Kitchen
                    - WalkInCloset
                    - FuseBox
                    - Shaft
                  to:
                    type: string
                    enum:
                    - Living
                    - Circulation
                    - Bed
                    - Bath
                    - WC
                    - Storage
                    - Kitchen
                    - WalkInCloset
                    - FuseBox
                    - Shaft
                  relation:
                    type: string
                    enum:
                    - required
                    - forbidden
                    default: required
                  minDoorWidth:
                    type: number
                    exclusiveMinimum: true
                    minimum: 0
                    description: Minimum nominal width of the connecting door; required rules only. Linear values
                      use geometry.units.
            requireConnected:
              type: boolean
              default: true
              description: All accessible rooms must have a placed-door path to the entrance. Closed shafts are
                exempt.
        turnstileToken:
          type: string
          description: Required only when Turnstile is configured.
    CompletedCandidate:
      type: object
      required:
      - seed
      - rooms
      properties:
        seed:
          type: integer
        rooms:
          type: array
          items:
            type: object
            required:
            - function
            - polygon
            properties:
              function:
                type: string
              polygon:
                type: array
                minItems: 3
                items:
                  $ref: '#/components/schemas/Point'
              id:
                type: integer
              metrics:
                type: object
                properties:
                  clearAreaM2:
                    type: number
                  largestClearRectangle:
                    type: array
                    minItems: 2
                    maxItems: 2
                    items:
                      type: number
                    description: Width and depth of the largest-area clear inscribed rectangle, in geometry.units;
                      orientation follows the canonical plan axes.
                  facadeLength:
                    type: number
                    exclusiveMinimum: true
                    minimum: 0
                    description: Total facade contact. Linear values use geometry.units.
        entrance:
          $ref: '#/components/schemas/Point'
        requirementsSatisfied:
          type: boolean
        walls:
          type: array
          items:
            $ref: '#/components/schemas/Polyline'
        doors:
          type: array
          items:
            type: object
            properties:
              room:
                type: integer
              location:
                $ref: '#/components/schemas/Point'
              width:
                type: number
                exclusiveMinimum: true
                minimum: 0
                description: Nominal door width. Linear values use geometry.units.
              entrance:
                type: boolean
              polylines:
                type: array
                items:
                  $ref: '#/components/schemas/Polyline'
        connections:
          type: array
          items:
            type: object
            properties:
              from:
                type: integer
              to:
                type: integer
              width:
                type: number
                exclusiveMinimum: true
                minimum: 0
                description: Nominal connecting door width. Linear values use geometry.units.
              location:
                $ref: '#/components/schemas/Point'
    CompletedPlanResponse:
      type: object
      required:
      - ok
      - requestId
      - modelVersion
      - units
      - requestedAttempts
      - uniqueCandidates
      - candidates
      properties:
        ok:
          type: boolean
        requestId:
          type: string
        modelVersion:
          type: string
        units:
          type: string
        requestedAttempts:
          type: integer
        uniqueCandidates:
          type: integer
        candidates:
          type: array
          items:
            $ref: '#/components/schemas/CompletedCandidate'
        usage:
          $ref: '#/components/schemas/Usage'
        jobId:
          type: string
          format: uuid
        rejectedCandidates:
          type: array
          items:
            type: object
        pipeline:
          type: string
        appliedSettings:
          type: object
          properties:
            wallThicknesses:
              $ref: '#/components/schemas/PlanRequest/properties/settings/properties/wallThicknesses'
            doorWidths:
              $ref: '#/components/schemas/PlanRequest/properties/settings/properties/doorWidths'
            entrance:
              type: object
              description: Effective entrance policy. The shift is zero when movement is disabled.
              properties:
                adjustEntrance:
                  type: boolean
                maxEntranceShift:
                  type: number
                  minimum: 0
                  description: Maximum permitted movement in either direction, in geometry.units.
    PlanJob:
      type: object
      required:
      - ok
      - jobId
      - status
      - statusUrl
      - requestedAttempts
      - pollAfterMs
      properties:
        ok:
          type: boolean
        jobId:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - queued
          - running
          - succeeded
          - failed
        statusUrl:
          type: string
        pollAfterMs:
          type: integer
        requestedAttempts:
          type: integer
          minimum: 1
          maximum: 32
        httpStatus:
          type: integer
          description: Terminal result status. Failed generation can be 422 even though polling itself returns HTTP
            200.
        result:
          $ref: '#/components/schemas/CompletedPlanResponse'
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: object
    Country:
      type: string
      enum:
      - AT
      - BE
      - CH
      - DE
      - DK
      - ES
      - FR
      - GB
      - NL
      - 'NO'
      - OTHER
      - SE
      - UK
      description: Country preference for supported apartment layouts. US and Canadian inputs are unsupported and
        return UNSUPPORTED_COUNTRY.
    OperationResult:
      type: object
      additionalProperties: false
      properties:
        ok:
          type: boolean
    AccountEmail:
      type: object
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
          maxLength: 254
      required:
      - email
    VerificationToken:
      type: object
      additionalProperties: false
      properties:
        token:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
      required:
      - token
    AccountSession:
      type: object
      additionalProperties: false
      properties:
        ok:
          type: boolean
        accountToken: *id004
      required:
      - ok
      - accountToken
    AccountKey:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        name:
          type: string
        prefix:
          type: string
        createdAt:
          type: integer
          description: Unix timestamp in seconds.
        status:
          type: string
          enum:
          - ACTIVE
          - REVOKED
    NewAccountKey:
      type: object
      additionalProperties: false
      properties:
        ok:
          type: boolean
        apiKey:
          type: string
          readOnly: true
        message:
          type: string
      required:
      - ok
      - apiKey
    BillingRedirect:
      type: object
      additionalProperties: false
      properties:
        ok:
          type: boolean
        url:
          type: string
          format: uri
        livemode:
          type: boolean
      required:
      - ok
      - url
    Account:
      type: object
      additionalProperties: false
      properties:
        ok:
          type: boolean
        email:
          type: string
          format: email
        keys:
          type: array
          items:
            $ref: '#/components/schemas/AccountKey'
        trial:
          allOf:
          - $ref: '#/components/schemas/Usage'
          nullable: true
        usage:
          allOf:
          - $ref: '#/components/schemas/Usage'
          nullable: true
        subscription:
          type: object
          nullable: true
          properties:
            status:
              type: string
            usage:
              $ref: '#/components/schemas/Usage'
            cancelAtPeriodEnd:
              type: boolean
        billingEnabled:
          type: boolean
        livemode:
          type: boolean
          nullable: true
        checkoutPending:
          type: boolean
        canCreateKey:
          type: boolean
    InputValidation:
      type: object
      additionalProperties: false
      properties:
        ok:
          type: boolean
        valid:
          type: boolean
        attemptsReserved:
          type: integer
          enum:
          - 0
        summary:
          type: object
          additionalProperties: false
          properties:
            outlineAreaM2:
              type: number
            requestedAttempts:
              type: integer
            units:
              type: string
            roomRequirementTypes:
              type: array
              items:
                type: string
      required:
      - ok
      - valid
      - attemptsReserved
      - summary
