openapi: 3.1.0
info:
  title: DesignGen Developer API
  version: 1.0.0
  description: |
    Durable Ultra Max generation, prompt enhancement, reference assets,
    screenshot-to-design, vectorization, webhook, and image-processing APIs.
    API-key requests are subject to per-key, per-project, and per-IP minute
    limits. Rate-limited responses use HTTP 429 and include Retry-After.
servers:
  - url: https://platform.trydesigngen.com
    description: Invite-only partner platform
  - url: http://localhost:3000
    description: Local development
security:
  - bearerAuth: []
paths:
  /v1:
    get:
      tags: [Platform]
      summary: Discover the DesignGen Developer API
      description: Returns the public version, documentation, contract, and supported capability names.
      security: []
      responses:
        '200':
          description: Public API discovery document

  /v1/developer/projects:
    get:
      tags: [Projects]
      summary: List developer projects
      description: Requires a Firebase bearer token from an eligible DesignGen account. Ensures a default project exists.
      responses:
        '200':
          description: Project collection
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProjectListResponse' }
        '403': { description: Authentication or developer access denied }
    post:
      tags: [Projects]
      summary: Create a developer project
      description: Requires a Firebase bearer token. Projects isolate environments, keys, jobs, assets, webhooks, and limits.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateProjectRequest' }
      responses:
        '201':
          description: Project created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProjectResponse' }
        '400': { description: Invalid project policy }
        '403': { description: Authentication or developer access denied }

  /v1/assets/uploads:
    post:
      tags: [Assets]
      summary: Create a direct private upload
      description: |
        Creates a pending project asset and returns a one-time, short-lived signed PUT URL.
        Upload the exact bytes directly to private storage with every returned header, then call
        the completion endpoint. The URL is bound to one object, method, MIME type, exact byte
        length, and ten-minute expiry. Do not log or persist it. SVG files must omit DOCTYPE
        declarations, scripts, external resources, and other active content.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateAssetUploadRequest' }
      responses:
        '201':
          description: Pending asset and signed upload capability
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AssetUploadIntentResponse' }
        '400': { description: Invalid file metadata or checksum }
        '401': { description: Unauthorized }
        '403': { description: Scope, plan, beta admission, or creation gate denied }

  /v1/assets:
    post:
      tags: [Assets]
      summary: Store a small private source or reference image
      description: |
        Compatibility path for one image data URL or, only when explicitly enabled, one HTTPS
        remote URL. Prefer the direct-upload flow for files because request bodies traverse the web
        function. Both inputs receive the same type, full-decode, pixel, and SVG safety validation
        as direct uploads. Remote import remains disabled until network-level egress enforcement is
        ready.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [projectId, fileName, purpose]
              properties:
                projectId: { type: string }
                imageUrl: { type: string, format: uri }
                dataUrl: { type: string }
                fileName: { type: string }
                purpose: { type: string, enum: [source, reference] }
      responses:
        '201':
          description: Private asset stored
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AssetResponse' }
        '400': { description: Invalid or unsafe input }
        '401': { description: Unauthorized }
        '403': { description: Scope or plan does not allow the request }
    get:
      tags: [Assets]
      summary: List project assets
      parameters:
        - { $ref: '#/components/parameters/ProjectId' }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200': { description: Asset collection }

  /v1/assets/{assetId}/complete:
    post:
      tags: [Assets]
      summary: Verify and complete a direct upload
      description: |
        Verifies that the exact private object exists and matches the declared byte size,
        content type, fully decoded image, safety rules, and optional SHA-256. Verification is
        leased to one request and bound to one immutable Storage generation. Only a successful
        completion changes the asset from pending_upload to available. Repeating completion for
        an available asset is idempotent.
      parameters:
        - { $ref: '#/components/parameters/AssetId' }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CompleteAssetUploadRequest' }
      responses:
        '200':
          description: Verified private asset
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AssetResponse' }
        '400': { description: Size or checksum does not match the upload intent }
        '409': { description: Object has not been uploaded yet }
        '410': { description: Upload capability or asset has expired }
        '413': { description: Uploaded object exceeds the 20 MB limit }
        '415': { description: Object type, signature, or SVG safety validation failed }

  /v1/assets/{assetId}:
    get:
      tags: [Assets]
      summary: Retrieve asset metadata
      parameters:
        - { $ref: '#/components/parameters/AssetId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: Asset metadata }
        '404': { description: Asset not found in the authenticated project }
    delete:
      tags: [Assets]
      summary: Delete an asset and its private object
      parameters:
        - { $ref: '#/components/parameters/AssetId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '204': { description: Asset deleted }

  /v1/assets/{assetId}/content:
    get:
      tags: [Assets]
      summary: Download a private asset
      description: Returns a private, no-store 307 redirect to a short-lived signed object URL. Do not persist the redirected URL.
      parameters:
        - { $ref: '#/components/parameters/AssetId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '307': { description: Short-lived signed download redirect }
        '404': { description: Asset is unknown, outside the project, deleted, or expired }

  /v1/assets/{assetId}/save:
    post:
      tags: [Assets]
      summary: Save an asset to the Brand Library
      description: |
        Idempotently copies an available project asset into the authenticated owner's
        private Brand Library, then removes the developer asset's automatic expiry.
        If brandId is omitted, the owner's active brand is used. The developer asset is
        not marked as saved unless the Brand Library record and private object exist.
      parameters:
        - { $ref: '#/components/parameters/AssetId' }
        - { $ref: '#/components/parameters/ProjectId' }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SaveAssetToBrandLibraryRequest' }
      responses:
        '200':
          description: Developer asset metadata and its durable Brand Library identity
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SaveAssetToBrandLibraryResponse' }
        '400': { description: 'Invalid brand, description, or tags' }
        '403': { description: Brand ownership, plan quota, or project access denied }
        '404': { description: Asset not found in the authenticated project }
        '410': { description: Asset has already expired or been deleted }

  /v1/prompts/enhance:
    post:
      tags: [Prompt Writer]
      summary: Produce the effective generation prompt
      description: Returns original and enhanced text, locked copy, reference-role instructions, warnings, and writer version without creating a generation.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PromptEnhancementRequest' }
      responses:
        '200': { description: Prompt trace }
        '400': { description: Invalid prompt or reference roles }

  /v1/generations:
    post:
      tags: [Generations]
      summary: Queue a durable Ultra Max generation
      description: Creates the durable job and idempotency record before provider dispatch. Production dispatch is feature gated.
      parameters:
        - { $ref: '#/components/parameters/IdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GenerationRequest' }
            examples:
              promptToDesign:
                summary: Prompt to standalone artwork
                value:
                  projectId: project_example
                  operation: generate
                  quality: ultra-max
                  prompt:
                    text: A one-color vintage racing emblem for a black shirt
                    mode: enhance
                    lockedText: [NIGHT SHIFT]
                  references: []
                  output:
                    resolution: 2K
                    aspectRatio: '1:1'
                    format: png
                    count: 1
                    background: transparent
              withReference:
                summary: Generate with a style reference
                value:
                  projectId: project_example
                  operation: generate_with_reference
                  quality: ultra-max
                  prompt:
                    text: A premium club graphic
                    mode: enhance
                    lockedText: []
                  references:
                    - assetId: asset_style_example
                      role: style
                  output:
                    resolution: 2K
                    aspectRatio: '1:1'
                    format: png
                    count: 1
                    background: transparent
              screenshotToDesign:
                summary: Rebuild a screenshot as standalone artwork
                value:
                  projectId: project_example
                  operation: screenshot_to_design
                  quality: ultra-max
                  prompt:
                    text: Rebuild this as standalone apparel artwork
                    mode: enhance
                    lockedText: []
                  references:
                    - assetId: asset_source_example
                      role: source
                  output:
                    resolution: 2K
                    aspectRatio: '1:1'
                    format: png
                    count: 1
                    background: transparent
      responses:
        '202': { description: Durable job accepted }
        '402': { description: Insufficient credits }
        '409': { description: Idempotency key conflict }
        '429': { description: Project daily credit cap reached }
    get:
      tags: [Generations]
      summary: List durable generation jobs
      parameters:
        - { $ref: '#/components/parameters/ProjectId' }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Status' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200': { description: Generation collection }

  /v1/generations/{generationId}:
    get:
      tags: [Generations]
      summary: Retrieve canonical generation state
      parameters:
        - { $ref: '#/components/parameters/GenerationId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: Generation, prompt trace, billing state, and output asset IDs }
        '404': { description: Generation not found in the authenticated project }
    delete:
      tags: [Generations]
      summary: Tombstone a generation and delete its outputs
      parameters:
        - { $ref: '#/components/parameters/GenerationId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '204': { description: Generation deleted }

  /v1/generations/{generationId}/cancel:
    post:
      tags: [Generations]
      summary: Cancel a queued or running generation
      parameters:
        - { $ref: '#/components/parameters/GenerationId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: Canonical cancelled state }
        '409': { description: Generation is no longer cancellable }

  /v1/generations/{generationId}/feedback:
    post:
      tags: [Generations]
      summary: Record generation feedback
      description: Adds an owner-scoped verdict to a generation. Requires the generations:create scope.
      parameters:
        - { $ref: '#/components/parameters/GenerationId' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [verdict]
              properties:
                verdict: { type: string, enum: [up, down] }
                issues: { type: array, maxItems: 10, items: { type: string, maxLength: 64 } }
                comment: { type: string, maxLength: 2000 }
      responses:
        '201': { description: Feedback recorded }
        '400': { description: Invalid feedback }
        '404': { description: Generation not found }

  /v1/generations/{generationId}/files/{format}:
    get:
      tags: [Generations]
      summary: Download a completed generation file
      description: Returns a private, no-store 307 redirect for the format created by the generation.
      parameters:
        - { $ref: '#/components/parameters/GenerationId' }
        - { $ref: '#/components/parameters/ProjectId' }
        - { $ref: '#/components/parameters/GenerationFormat' }
      responses:
        '307': { description: Short-lived signed download redirect }
        '404': { description: Completed generation file is not available }

  /v1/generations/{generationId}/vectorizations:
    post:
      tags: [Vectorizations]
      summary: Vectorize the first output of a completed generation
      parameters:
        - { $ref: '#/components/parameters/GenerationId' }
        - { $ref: '#/components/parameters/IdempotencyKey' }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GenerationVectorizationRequest' }
      responses:
        '202': { description: Durable vectorization job accepted }
        '404': { description: Completed generation output not found }
        '409': { description: Idempotency key conflict }

  /v1/vectorizations:
    post:
      tags: [Vectorizations]
      summary: Queue the new durable vector pipeline
      parameters:
        - { $ref: '#/components/parameters/IdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [projectId, sourceAssetId]
              properties:
                projectId: { type: string }
                sourceAssetId: { type: string }
                sourceGenerationId: { type: string }
                profile: { type: string, enum: [print, detail, simple], default: print }
                formats:
                  type: array
                  items: { type: string, enum: [svg, pdf, png, eps, dxf] }
                  default: [svg, pdf, png]
      responses:
        '202': { description: Vectorization job accepted }
        '409': { description: Idempotency key conflict }
    get:
      tags: [Vectorizations]
      summary: List vectorization jobs
      parameters:
        - { $ref: '#/components/parameters/ProjectId' }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Status' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200': { description: Vectorization collection }

  /v1/vectorizations/{vectorizationId}:
    get:
      tags: [Vectorizations]
      summary: Retrieve canonical vectorization state
      parameters:
        - { $ref: '#/components/parameters/VectorizationId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: Vectorization state and output asset IDs }
        '404': { description: Vectorization not found in the authenticated project }
    delete:
      tags: [Vectorizations]
      summary: Tombstone a vectorization and delete its outputs
      parameters:
        - { $ref: '#/components/parameters/VectorizationId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '204': { description: Vectorization deleted }
        '404': { description: Vectorization not found }

  /v1/vectorizations/{vectorizationId}/files/{format}:
    get:
      tags: [Vectorizations]
      summary: Download a completed vector output
      parameters:
        - { $ref: '#/components/parameters/VectorizationId' }
        - { $ref: '#/components/parameters/ProjectId' }
        - { $ref: '#/components/parameters/VectorFormat' }
      responses:
        '307': { description: Short-lived signed download redirect }
        '404': { description: Requested vector format is not available }

  /v1/developer/webhooks:
    post:
      tags: [Webhooks]
      summary: Create a signed webhook endpoint
      description: The destination must be a public HTTPS URL on a deployment-approved outbound hostname. Redirects, credentials, custom ports, and private destinations are denied.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [projectId, url, events]
              properties:
                projectId: { type: string }
                url: { type: string, format: uri }
                events:
                  type: array
                  items:
                    type: string
                    enum:
                      [
                        generation.queued,
                        generation.started,
                        generation.completed,
                        generation.blocked,
                        generation.failed,
                        generation.cancelled,
                        vectorization.completed,
                        vectorization.failed,
                        asset.expiring,
                        usage.limit_warning,
                      ]
      responses:
        '201': { description: Endpoint created; signing secret is returned once }
        '400': { description: Unsafe or invalid webhook URL }
    get:
      tags: [Webhooks]
      summary: List project webhook endpoints
      parameters:
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: Webhook endpoint collection }

  /v1/developer/webhooks/{webhookId}:
    delete:
      tags: [Webhooks]
      summary: Disable a webhook endpoint
      parameters:
        - { $ref: '#/components/parameters/WebhookId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '204': { description: Webhook disabled }
        '404': { description: Webhook not found }

  /v1/developer/webhooks/{webhookId}/test:
    post:
      tags: [Webhooks]
      summary: Send a signed test event
      parameters:
        - { $ref: '#/components/parameters/WebhookId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: Test delivery attempt recorded }
        '404': { description: Webhook not found }

  /v1/developer/webhooks/{webhookId}/deliveries:
    get:
      tags: [Webhooks]
      summary: List webhook delivery attempts
      parameters:
        - { $ref: '#/components/parameters/WebhookId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: Delivery attempt collection }
        '404': { description: Webhook not found }

  /v1/developer/webhooks/{webhookId}/deliveries/{deliveryId}/retry:
    post:
      tags: [Webhooks]
      summary: Manually retry a recorded webhook delivery
      parameters:
        - { $ref: '#/components/parameters/WebhookId' }
        - { $ref: '#/components/parameters/DeliveryId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200': { description: New delivery attempt recorded }
        '404': { description: Webhook, delivery, or retained event not found }

  /v1/dtf-halftone:
    post:
      tags:
        - DTF Halftone
      summary: Run DTF halftone processing
      description: |
        Processes an input image URL using ordered dot-screen DTF halftoning.
        Requires Authorization bearer token (Firebase ID token or developer API key)
        from a Pro or Enterprise account.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DtfHalftoneRequest'
            examples:
              default:
                value:
                  imageUrl: https://example.com/input.png
                  dtfHalftone: 40
                  dtfHalftoneAngle: 90
      responses:
        '200':
          description: Successful processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DtfHalftoneSuccessResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: Insufficient tokens
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required or insufficient API key scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Source image too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Source image validation/decode failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: Upstream worker failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/design-analysis:
    post:
      x-designgen-status: implemented
      tags:
        - Design Analysis
      summary: Analyze a design image for naming, search, and mockup metadata
      description: |
        Analyzes an HTTPS image URL and returns structured naming, search, and mockup recommendation data.
        Requires Authorization bearer token (Firebase ID token or developer API key)
        from a Pro or Enterprise account. The beta contract has typed decoded-image facts,
        a visual assessment, request profile, print-target controls, and provenance. Treat model
        assessment fields as suggestions requiring caller review; print measurements are rules-based.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DesignAnalysisRequest'
            example:
              imageUrl: https://assets.example.test/artwork.png
              designPrompt: One-color shop shirt artwork
              profile: standard
              printTarget:
                width: 12
                height: 16
                unit: in
                method: dtf
              metadata:
                externalArtworkId: ams-art-42
      parameters:
        - in: header
          name: Idempotency-Key
          required: false
          description: Optional 8-200 character key. Reuse only with the exact same normalized request.
          schema: { type: string, minLength: 8, maxLength: 200 }
        - in: header
          name: X-Request-Id
          required: false
          description: Optional caller correlation ID using 8-128 letters, digits, underscores, or hyphens.
          schema: { type: string, minLength: 8, maxLength: 128, pattern: '^[A-Za-z0-9_-]+$' }
      responses:
        '200':
          description: Successful analysis
          headers:
            X-Request-Id:
              {
                description: Accepted caller correlation ID or server-generated request ID,
                schema: { type: string },
              }
            Idempotency-Replayed:
              {
                description: true when this response is the stored result of the same key/body,
                schema: { type: string, enum: ['true', 'false'] },
              }
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DesignAnalysisSuccessResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required or insufficient API key scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Idempotency key reused with a changed request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/remove-background:
    post:
      tags:
        - Remove Background
      summary: Remove the background from a graphic design (DG-R-BG-01)
      description: |
        Powered by DG-R-BG-01, DesignGen's purpose-built background-removal
        model for graphic designs. Removes flat or near-flat backgrounds from
        artwork (text, logos, illustrations) with anti-aliased edges. Every
        response includes `model: "DG-R-BG-01"`. Not intended for photographs —
        a quality router declines unsuitable images with HTTP 422 and a QA
        report, at no token charge. Requires Authorization bearer token
        (Firebase ID token or developer API key with the `background_removal`
        scope) from a Pro or Enterprise account.

        **Pricing notice:** free during the launch period; transitioning to
        25 tokens ($0.25) per successful removal. The response `tokenCost`
        field always reflects the rate actually charged.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RemoveBackgroundRequest'
            examples:
              byUrl:
                value:
                  imageUrl: https://example.com/design.png
                  options:
                    interiorMode: auto
                    quality: high
      responses:
        '200':
          description: Background removed (route `native`) or input already transparent (route `skipped_already_transparent`, not charged)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveBackgroundSuccessResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: Insufficient tokens
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required or insufficient API key scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Image not suitable for background removal (photograph or full-bleed pattern); includes QA report, not charged
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveBackgroundDeclinedResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/remove-background/feedback:
    post:
      tags:
        - Remove Background
      summary: Submit feedback on a DG-R-BG-01 result
      description: |
        Free — feedback is never charged. Attach a thumbs verdict, a granular
        rating (1–5, 1–7, or 1–10 scale), structured issue tags, and/or
        free-text fields to a prior result's `requestId`. At least one signal
        is required. Feedback is accepted only from the account that created
        the result, and entries are append-only. Rated results feed directly
        into model improvement.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RemoveBackgroundFeedbackRequest'
            examples:
              thumbsDown:
                value:
                  requestId: bgr_3f2a0c
                  verdict: down
                  issues: [background_left, edges_jagged]
                  comment: Gray halo left around the lettering
                  expected: Clean cutout with no halo
      responses:
        '200':
          description: Feedback recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveBackgroundFeedbackResponse'
        '400':
          description: No feedback signal provided or invalid payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient API key scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown requestId (or owned by another account)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/remove-background/editor-sessions:
    post:
      tags:
        - Remove Background
      summary: Create a hosted editor session (Background Removal Studio)
      description: |
        Registers an input image and returns a hosted editor URL plus a
        capability token. Send your end-user to the URL — they click-fix
        regions in our white-label interface (no account needed) — then
        poll GET /editor-sessions/{id} for the finished cutout, or listen
        for the `dgr-bg-editor:complete` postMessage in popup/iframe
        embeds. Sessions expire after 24 hours, allow up to 30 processing
        runs, and every successful run bills the session owner.
        Requires API-key or Firebase auth from a Pro/Enterprise account.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EditorSessionCreateRequest'
      responses:
        '200':
          description: Session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EditorSessionCreateResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required or insufficient API key scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: Input image could not be stored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/remove-background/editor-sessions/{editorSessionId}:
    get:
      tags:
        - Remove Background
      summary: Read a hosted editor session
      description: |
        Two credentials are accepted: the session's capability token via
        `?t=` (used by the hosted editor), or the owning account's API
        key / Firebase auth (used to poll for the final result). Returns
        the session status, run count, and `latestResult` with the current
        cutout URL and requestId.
      parameters:
        - name: editorSessionId
          in: path
          required: true
          schema:
            type: string
        - name: t
          in: query
          required: false
          schema:
            type: string
          description: Capability token (end-user access; omit when using owner auth)
      responses:
        '200':
          description: Session state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EditorSessionStateResponse'
        '401':
          description: Unauthorized (owner-auth path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown session or bad token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/remove-background/editor-sessions/{editorSessionId}/process:
    post:
      tags:
        - Remove Background
      summary: Run the removal for an editor session (token auth)
      description: |
        Called by the hosted editor on load and on each "Apply". Runs
        DG-R-BG-01 on the session's stored input with the session options
        plus any keep/punch points, bills the session owner on native
        success, and records the run. Capped by the session's run limit.
      parameters:
        - name: editorSessionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EditorProcessRequest'
      responses:
        '200':
          description: Processed result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EditorProcessResponse'
        '402':
          description: Session owner has insufficient tokens
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown session or bad token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Session already completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          description: Session expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Image not suitable (QA router declined)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveBackgroundDeclinedResponse'
        '429':
          description: Session run limit reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/remove-background/editor-sessions/{editorSessionId}/complete:
    post:
      tags:
        - Remove Background
      summary: Finalize an editor session (token auth)
      description: |
        Marks the session completed after at least one processing run.
        The owner then reads latestResult from GET /editor-sessions/{id}.
      parameters:
        - name: editorSessionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token:
                  type: string
      responses:
        '200':
          description: Session completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EditorSessionStateResponse'
        '404':
          description: Unknown session or bad token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Nothing processed yet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          description: Session expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/developer/api-keys:
    get:
      tags:
        - Developer Keys
      summary: List developer API keys
      description: Requires Firebase bearer token from a Pro or Enterprise account.
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 100
      responses:
        '200':
          description: API key list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListApiKeysResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    post:
      tags:
        - Developer Keys
      summary: Create developer API key
      description: |
        Requires Firebase bearer token from a Pro or Enterprise account.
        Plaintext API key is returned once and cannot be fetched again.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
            examples:
              dtfOnly:
                value:
                  name: Production Integration
                  scopes:
                    - dtf_halftone
              wildcard:
                value:
                  name: Trusted Backend
                  scopes:
                    - '*'
      responses:
        '200':
          description: API key created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateApiKeyResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/developer/api-keys/{keyId}:
    delete:
      tags:
        - Developer Keys
      summary: Revoke developer API key
      description: Requires Firebase bearer token from a Pro or Enterprise account.
      parameters:
        - in: path
          name: keyId
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Key revoked
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - keyId
                  - status
                properties:
                  success:
                    type: boolean
                    const: true
                  keyId:
                    type: string
                  status:
                    type: string
                    const: revoked
        '400':
          description: Missing/invalid key ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Key not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/developer/usage:
    get:
      tags:
        - Developer Usage
      summary: List developer usage history
      description: |
        Requires a Firebase bearer token or an API key with `usage_read` from a Pro or
        Enterprise account. Returns normal Developer API request history plus a separate,
        account-wide Operator credit ledger. Only `settledCredits` are actual spend;
        `reservedCredits` are in-flight exposure and `releasedCredits` are refunded or
        released attempts. The `apiKeyId` filter applies only to request history because
        Operator credit records are not attributed to an individual API key.
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 25
        - in: query
          name: page
          schema:
            type: integer
            minimum: 1
            default: 1
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
            description: Optional. If provided, overrides page.
        - in: query
          name: apiKeyId
          schema:
            type: string
        - in: query
          name: startDate
          schema:
            type: string
            format: date-time
          description: ISO datetime or YYYY-MM-DD
        - in: query
          name: endDate
          schema:
            type: string
            format: date-time
          description: ISO datetime or YYYY-MM-DD (interpreted as end-of-day)
      responses:
        '200':
          description: Usage history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListUsageResponse'
        '400':
          description: Invalid date/filter params
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Pro subscription required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/operator/uploads:
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Upload artwork for an Operator run
      description: |
        Invite-only Operator beta. Requires Pro or Enterprise developer access, Operator access,
        and operator_write for API keys. Files are owned by the authenticated account; projectId
        is not used. Accepts PNG, JPEG, WebP, SVG, or PDF, up to 25 MB. For files approaching the
        web request limit, use JSON action sign, PUT the bytes to upload_url with the declared
        Content-Type, then action complete. Do not log signed upload URLs.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: { type: string, format: binary }
          application/json:
            schema:
              oneOf:
                - type: object
                  required: [action, filename, mime_type, size_bytes]
                  properties:
                    action: { const: sign }
                    filename: { type: string }
                    mime_type: { type: string }
                    size_bytes: { type: integer, minimum: 1, maximum: 26214400 }
                - type: object
                  required: [action, storage_path, mime_type]
                  properties:
                    action: { const: complete }
                    storage_path: { type: string }
                    mime_type: { type: string }
      responses:
        '200':
          description: Signed PUT upload_url, storage_path and filename; upload bytes then complete
        '201':
          description: Registered artifact_id, preview_url and filename; use artifact_id as attachmentRef
        '400': { description: Invalid upload request }
        '401': { description: Unauthorized }
        '403': { description: Developer access, Operator entitlement, or write scope denied }
        '413': { description: File exceeds upload limit }
        '415': { description: Unsupported file type }

  /v1/operator/runs:
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: List the authenticated account's Operator runs
      description: Requires Pro or Enterprise developer access and operator_read for API keys.
      parameters:
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        '200': { description: Object with runs array of run summaries }
        '401': { description: Unauthorized }
        '403': { description: Developer access or read scope denied }
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Start an asynchronous Operator workflow
      description: |
        Enroll in the Operator beta before integrating. Requires Pro or Enterprise developer
        access, Operator access and operator_write for API keys. The beta contract accepts
        `print_file_fix`, legacy `dtf_file_fix`, or `screenshot_to_design`, always with saved
        permissions and account-owned uploads. Print-file requests may opt into `approvalMode:
        preview`: Operator prepares a review preview, pauses on a decision bound to that exact
        artifact version, and starts final vectorization, 600-DPI preparation, QA, and export
        only after `allow_once`. `maxCredits` is an optional hard per-run ceiling that may lower,
        never raise, the account entitlement. A completed run does not establish production
        readiness: read lifecycle, artifacts, pending inputs/decisions, and—when applicable—the
        production package. This endpoint does not use projectId.
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          description: Reuse for retries of the same request; changing the request with this key returns 409.
          schema: { type: string, minLength: 8, maxLength: 200, pattern: '^[A-Za-z0-9._:-]+$' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [workflow]
              properties:
                workflow:
                  { type: string, enum: [print_file_fix, dtf_file_fix, screenshot_to_design] }
                attachmentRef:
                  {
                    type: string,
                    minLength: 1,
                    maxLength: 200,
                    description: One account-owned artifact reference.,
                  }
                attachmentRefs:
                  {
                    type: array,
                    minItems: 1,
                    maxItems: 20,
                    items: { type: string, minLength: 1, maxLength: 200 },
                    description: One or more account-owned artifact references.,
                  }
                targetWidthIn:
                  {
                    type: number,
                    exclusiveMinimum: 0,
                    maximum: 24,
                    description: Required for print_file_fix and legacy dtf_file_fix; invalid for screenshot_to_design.,
                  }
                approvalMode:
                  {
                    type: string,
                    enum: [preview],
                    description: Optional exact-version preview gate for print-file workflows; invalid for screenshot_to_design.,
                  }
                maxCredits:
                  {
                    type: integer,
                    minimum: 1,
                    maximum: 100000,
                    description: Optional hard run ceiling in DesignGen credits; it cannot exceed the account's entitlement.,
                  }
                modelTier: { const: light_us, default: light_us }
                instructions: { type: string, maxLength: 4000, default: '' }
                title: { type: string, minLength: 1, maxLength: 200 }
            example:
              workflow: print_file_fix
              attachmentRef: uploaded-artifact-id
              targetWidthIn: 12
              approvalMode: preview
              maxCredits: 500
              modelTier: light_us
              instructions: Preserve the original lettering and colors.
      responses:
        '201': { description: Object containing run.id, run.status and workflow_started }
        '200': { description: Existing run returned on an identical idempotent retry }
        '400':
          description: 'Invalid workflow, attachment reference, physical width, approval mode, credit ceiling, model tier, or idempotency key'
        '401': { description: Unauthorized }
        '403': { description: Developer access, Operator entitlement, or write scope denied }
        '404': { description: Attachment not found for this account }
        '409':
          {
            description: Idempotency key reused with a different request,
            including a changed approval mode or credit ceiling,
          }
        '502':
          {
            description: Run persisted but workflow startup failed; retry the identical request with the same key,
          }

  /v1/operator/runs/{runId}:
    parameters:
      - in: path
        name: runId
        required: true
        schema: { type: string }
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Read Operator lifecycle, artifacts, and pending input or approval
      description: Requires operator_read for API keys and developer access. Runs are account-owned; another account's run returns 404. The response includes an ETag. Send If-Match with a subsequent state-changing request to fail closed if the run changed.
      responses:
        '200':
          {
            description: run,
            plan and steps; run includes artifacts,
            deliverables and pending decisions,
          }
        '401': { description: Unauthorized }
        '403': { description: Developer access or read scope denied }
        '404': { description: Run not found }

  /v1/operator/runs/{runId}/events:
    parameters:
      - in: path
        name: runId
        required: true
        schema: { type: string }
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Poll the durable Operator event timeline
      description: Requires operator_read and developer access. Visibility is capped at the account's entitlement.
      parameters:
        - in: query
          name: after
          schema: { type: integer, minimum: 0, default: 0 }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 500, default: 200 }
      responses:
        '200':
          { description: run_id, events, cursor and has_more; use cursor as the next after value }
        '400': { description: Invalid cursor }
        '401': { description: Unauthorized }
        '403': { description: Developer access or read scope denied }
        '404': { description: Run not found }

  /v1/operator/runs/{runId}/instructions:
    parameters:
      - in: path
        name: runId
        required: true
        schema: { type: string }
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Send an instruction to an active Operator run
      description: Requires operator_write and developer access. Terminal runs reject instructions with 409.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type]
              properties:
                type: { enum: [cancel, pause, resume, correction, follow_up, information] }
                content:
                  type: string
                  maxLength: 20000
                  description: Required and nonempty for correction, follow_up and information.
      responses:
        '202': { description: Queued instruction and workflow signal status }
        '400': { description: Invalid instruction }
        '401': { description: Unauthorized }
        '403': { description: Developer access or write scope denied }
        '404': { description: Run not found }
        '409': { description: Run is already terminal }

  /v1/operator/decisions/{decisionId}:
    parameters:
      - in: path
        name: decisionId
        required: true
        schema: { type: string }
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Answer an Operator permission request
      description: |
        Requires operator_write and developer access. The decision's run must belong to the
        caller. For the public preview gate, send `allow_once` only after reviewing the exact
        artifact ID and version in the runtime binding. A stale, superseded, missing, or
        cross-run preview returns 409 and final work does not start. Preview decisions are
        one-run decisions and cannot create a saved grant. Other permissions retain the
        documented `always_allow` behavior.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [decision]
              properties:
                decision: { enum: [allow_once, always_allow, deny] }
      responses:
        '200': { description: Permission decision recorded }
        '400': { description: Invalid decision }
        '401': { description: Unauthorized }
        '403': { description: Developer access or write scope denied }
        '404': { description: Decision not found for this account }
        '409':
          {
            description: Decision already recorded,
            or the bound preview artifact is stale or unavailable,
          }

  /v1/operator/runs/{runId}/production-package:
    parameters:
      - in: path
        name: runId
        required: true
        schema: { type: string }
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Inspect the verified DTF production package
      description: |
        Requires operator_read and developer access. Returns 202 while active, with Retry-After: 10.
        Terminal runs return 200 even when ready is false. A completed status alone is insufficient.
        ready requires a completed run, verified matching SVG/PNG production lineage, transparency,
        checksums, requested physical width at 600 DPI and working download URLs. Deterministic
        checks do not replace artwork fidelity review. Failed, canceled or warning runs remain
        not ready. Signed download URLs are temporary; refetch this endpoint to refresh them.
      responses:
        '200':
          description: Terminal production manifest; inspect ready and warnings before using files
          content:
            application/json:
              schema:
                type: object
                required: [schema_version, run_id, status, ready, files, warnings]
                properties:
                  schema_version: { const: 1 }
                  run_id: { type: string }
                  status: { type: string }
                  ready: { type: boolean }
                  target_width_in: { type: number }
                  target_dpi: { const: 600 }
                  warnings: { type: array, items: { type: string } }
                  files:
                    type: array
                    items:
                      type: object
                      properties:
                        artifact_id: { type: string }
                        format: { enum: [svg, png] }
                        width_px: { type: [integer, 'null'] }
                        height_px: { type: [integer, 'null'] }
                        density_dpi: { type: [number, 'null'] }
                        effective_dpi: { type: [number, 'null'] }
                        checksum: { type: [string, 'null'] }
                        size_bytes: { type: [integer, 'null'] }
                        verified: { type: boolean }
                        download_url: { type: [string, 'null'] }
                        warnings: { type: array, items: { type: string } }
                        checks: { type: array, items: { type: object } }
                  summary: { type: [string, 'null'] }
                  verification: { type: string }
        '202':
          description: run_id, status and ready false; continue polling after Retry-After
          headers:
            Retry-After: { schema: { type: integer }, description: Seconds before polling again }
        '401': { description: Unauthorized }
        '403': { description: Developer access or read scope denied }
        '404': { description: Run not found }
        '409': { description: Run was not created with the DTF API workflow }

  /v1/operator/runs/{runId}/events/stream:
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Stream ordered persisted Operator events
      description: |
        Implemented beta SSE transport over the persisted event timeline. Resume from Last-Event-ID
        or `after`; the cursor polling endpoint remains authoritative for reconstruction. Streams
        terminate shortly after a terminal run and must not be treated as a final approval signal.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
        - in: query
          name: after
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        '200':
          {
            description: text/event-stream with ordered `operator-event` frames and a stream-end frame,
          }

  /v1/operator/runs/{runId}/artifacts/{artifactId}/download:
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Download one immutable Operator artifact version
      description: Implemented beta ownership-checked 307 redirect for a current artifact. The redirected URL is temporary and must not be persisted.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
        - in: path
          name: artifactId
          required: true
          schema: { type: string }
      responses:
        '307': { description: Short-lived authenticated download redirect }
        '404': { description: Artifact does not belong to this run/account }
        '503': { description: Artifact storage is temporarily unavailable }

  /v1/operator/runs/{runId}/artifacts:
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: List immutable artifacts and lineage for a run
      description: Implemented beta artifact index. Each item carries its immutable version, checksum, preview state, lineage, and an authenticated exact-artifact download route.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
      responses:
        '200':
          {
            description: run_id and artifact array; inspect final and superseded lineage before approval,
          }
        '412': { description: If-Match does not match the current run version }

  /v1/operator/runs/{runId}/attachments:
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Attach owned uploads while answering a current request
      description: Implemented beta. Binds existing account-owned artifacts to a current input decision; it cannot mutate a completed run.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [decision_ref, asset_refs]
              properties:
                decision_ref: { type: string }
                asset_refs: { type: array, minItems: 1, maxItems: 20, items: { type: string } }
                expected_plan_version: { type: integer, minimum: 1 }
                expected_request_sequence: { type: integer, minimum: 1 }
                values: { type: object, additionalProperties: { type: string } }
                content: { type: string, maxLength: 20000 }
      responses:
        '202': { description: Attachment answer queued; not completion }
        '409': { description: Input request is stale or run is terminal }
        '412': { description: If-Match does not match the current run version }

  /v1/operator/runs/{runId}/cancel:
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Queue cancellation for an active run
      description: Implemented beta cancellation request. It queues a lifecycle instruction and never claims to undo a completed external action.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
      responses:
        '202': { description: Cancellation instruction queued; read run state for terminal result }
        '409': { description: Run is terminal }
        '412': { description: If-Match does not match the current run version }

  /v1/operator/runs/{runId}/resume:
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Queue resume for a paused run
      description: Implemented beta resume request. The response acknowledges queuing, not completed work.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
      responses:
        '202': { description: Resume instruction queued }
        '409': { description: Run is terminal }
        '412': { description: If-Match does not match the current run version }

  /v1/operator/runs/{runId}/retry:
    post:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Create a fresh retry from a failed run
      description: Implemented beta retry. It creates a fresh run from immutable failed-run context; completed work and permissions are not replayed.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
        - { $ref: '#/components/parameters/IdempotencyKey' }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                asset_refs: { type: array, maxItems: 20, items: { type: string } }
                attachment_mode: { enum: [merge, replace] }
      responses:
        '201': { description: Fresh retry run created }
        '400': { description: Invalid retry request or missing idempotency key }
        '409': { description: Source run is not eligible for retry }
        '412': { description: If-Match does not match the current run version }

  /v1/operator/runs/{runId}/report:
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Download a user-safe run report
      description: Implemented beta read-only timeline/replay report. It redacts internal configuration and does not repeat paid work.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
      responses:
        '200': { description: User-safe report for the entitled visibility level }

  /v1/operator/runs/{runId}/tool-activity:
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: List user-safe tool activity
      description: Implemented beta activity strip. Raw tool inputs, provider credentials, and execution internals are not exposed.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
      responses:
        '200': { description: User-safe tool activity list }

  /v1/operator/runs/{runId}/tool-activity/{toolCallId}:
    get:
      x-designgen-status: implemented_beta
      tags: [Operator]
      summary: Read one user-safe tool activity item
      description: Implemented beta detailed activity entry with the same redaction boundary as the list.
      parameters:
        - { $ref: '#/components/parameters/RunId' }
        - in: path
          name: toolCallId
          required: true
          schema: { type: string }
      responses:
        '200': { description: User-safe activity item }
  /v1/designs/{designId}/revisions:
    get:
      tags: [Generations]
      summary: List durable design revisions
      description: Returns immutable revisions newest first. Provider model and request metadata are never serialized.
      parameters:
        - { $ref: '#/components/parameters/DesignId' }
        - { $ref: '#/components/parameters/ProjectId' }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: Paginated revision collection
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DesignRevisionListResponse' }
        '404': { description: Design not found in the authenticated project }

  /v1/designs/{designId}/revisions/{revisionId}:
    get:
      tags: [Generations]
      summary: Retrieve one durable design revision
      parameters:
        - { $ref: '#/components/parameters/DesignId' }
        - { $ref: '#/components/parameters/RevisionId' }
        - { $ref: '#/components/parameters/ProjectId' }
      responses:
        '200':
          description: Immutable revision
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DesignRevisionResponse' }
        '404': { description: Design revision not found in the authenticated project }

  /v1/designs/{designId}/current-revision:
    patch:
      tags: [Generations]
      summary: Select a saved revision
      description: Moves the current pointer with optimistic concurrency. This does not generate an image or consume credits.
      parameters:
        - { $ref: '#/components/parameters/DesignId' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [revisionId, expectedHeadVersion]
              properties:
                projectId: { type: string }
                revisionId: { type: string }
                expectedHeadVersion: { type: integer, minimum: 0 }
      responses:
        '200':
          description: Updated current revision pointer
        '409': { description: Design head changed; reload revisions before retrying }

components:
  parameters:
    RunId:
      name: runId
      in: path
      required: true
      schema: { type: string }
    DesignId:
      name: designId
      in: path
      required: true
      schema: { type: string }
    RevisionId:
      name: revisionId
      in: path
      required: true
      schema: { type: string }
    ProjectId:
      name: projectId
      in: query
      required: false
      schema: { type: string }
      description: Required for Firebase-authenticated requests targeting a non-default project. API keys are already project-bound.
    AssetId:
      name: assetId
      in: path
      required: true
      schema: { type: string }
    GenerationId:
      name: generationId
      in: path
      required: true
      schema: { type: string }
    VectorizationId:
      name: vectorizationId
      in: path
      required: true
      schema: { type: string }
    WebhookId:
      name: webhookId
      in: path
      required: true
      schema: { type: string }
    DeliveryId:
      name: deliveryId
      in: path
      required: true
      schema: { type: string }
    GenerationFormat:
      name: format
      in: path
      required: true
      schema: { type: string, enum: [png, webp] }
    VectorFormat:
      name: format
      in: path
      required: true
      schema: { type: string, enum: [svg, pdf, png, eps, dxf] }
    Limit:
      name: limit
      in: query
      required: false
      schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
    Status:
      name: status
      in: query
      required: false
      schema: { type: string, enum: [queued, running, completed, blocked, failed, cancelled] }
    Cursor:
      name: cursor
      in: query
      required: false
      schema: { type: string, maxLength: 256 }
      description: Send only a cursor returned as meta.nextCursor by a previous list response.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: { type: string, minLength: 8, maxLength: 200 }
      description: Reusing a key with an identical request replays the resource. Reusing it with a different request returns 409.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT or API Key

  schemas:
    DeveloperAsset:
      type: object
      required:
        [
          id,
          projectId,
          purpose,
          status,
          fileName,
          contentType,
          size,
          savedToLibrary,
          createdAt,
          updatedAt,
        ]
      properties:
        id: { type: string }
        projectId: { type: string }
        purpose:
          {
            type: string,
            enum: [source, reference, generated_output, vector_output, saved_library],
          }
        status:
          { type: string, enum: [pending_upload, available, expiring, expired, deleting, deleted] }
        fileName: { type: string }
        contentType: { type: string, enum: [image/png, image/jpeg, image/webp, image/svg+xml] }
        size: { type: integer, minimum: 0, maximum: 20971520 }
        width:
          oneOf: [{ type: integer, minimum: 1 }, { type: 'null' }]
        height:
          oneOf: [{ type: integer, minimum: 1 }, { type: 'null' }]
        savedToLibrary: { type: boolean }
        expiresAt:
          oneOf: [{ type: string, format: date-time }, { type: 'null' }]
        uploadExpiresAt:
          description: Present only while a direct upload is pending.
          oneOf: [{ type: string, format: date-time }, { type: 'null' }]
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    CreateAssetUploadRequest:
      type: object
      additionalProperties: false
      required: [fileName, contentType, sizeBytes]
      properties:
        projectId: { type: string }
        fileName: { type: string, minLength: 1, maxLength: 180 }
        contentType: { type: string, enum: [image/png, image/jpeg, image/webp, image/svg+xml] }
        sizeBytes: { type: integer, minimum: 1, maximum: 20971520 }
        purpose: { type: string, enum: [source, reference], default: reference }
        sha256:
          type: string
          pattern: '^[a-f0-9]{64}$'
          description: Optional lowercase SHA-256 of the exact file bytes.
    CompleteAssetUploadRequest:
      type: object
      additionalProperties: false
      properties:
        projectId: { type: string }
        sha256: { type: string, pattern: '^[a-f0-9]{64}$' }
    AssetResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data: { $ref: '#/components/schemas/DeveloperAsset' }
    AssetUploadIntentResponse:
      type: object
      required: [success, data, upload]
      properties:
        success: { type: boolean, const: true }
        data: { $ref: '#/components/schemas/DeveloperAsset' }
        upload:
          type: object
          required: [url, method, headers, expiresAt]
          properties:
            url:
              type: string
              format: uri
              description: Sensitive short-lived capability. Never log or persist it.
            method: { type: string, const: PUT }
            headers:
              type: object
              required: [Content-Type, x-goog-content-length-range, x-goog-if-generation-match]
              properties:
                Content-Type: { type: string }
                x-goog-content-length-range:
                  type: string
                  description: Inclusive exact byte range declared by the upload intent.
                x-goog-if-generation-match:
                  type: string
                  const: '0'
                  description: Makes the upload create-only so the signed capability cannot overwrite a verified object.
            expiresAt: { type: string, format: date-time }
    DeveloperProject:
      type: object
      required: [id, name, environment, retentionDays, createdAt, updatedAt]
      properties:
        id: { type: string }
        name: { type: string }
        environment: { type: string, enum: [development, production] }
        retentionDays: { type: integer, minimum: 1, maximum: 365 }
        dailyCreditCap:
          oneOf:
            - { type: integer, minimum: 1 }
            - { type: 'null' }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    CreateProjectRequest:
      type: object
      required: [name]
      properties:
        name: { type: string, minLength: 1, maxLength: 80 }
        environment: { type: string, enum: [development, production], default: development }
        retentionDays: { type: integer, minimum: 1, maximum: 365, default: 30 }
        dailyCreditCap:
          oneOf:
            - { type: integer, minimum: 1, maximum: 100000 }
            - { type: 'null' }
    ProjectResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data: { $ref: '#/components/schemas/DeveloperProject' }
    ProjectListResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data:
          type: array
          items: { $ref: '#/components/schemas/DeveloperProject' }
    DeveloperOutput:
      type: object
      properties:
        resolution: { type: string, enum: [1K, 2K, 4K], default: 2K }
        aspectRatio: { type: string, default: '1:1' }
        format: { type: string, enum: [png, webp], default: png }
        count: { type: integer, const: 1 }
        background: { type: string, enum: [transparent, opaque], default: transparent }
    DesignRevision:
      type: object
      required:
        [
          id,
          designId,
          parentRevisionId,
          rootRevisionId,
          sequence,
          operation,
          assetId,
          instruction,
          preservation,
          quality,
          output,
          createdAt,
        ]
      properties:
        id: { type: string }
        designId: { type: string }
        parentRevisionId:
          oneOf:
            - { type: string }
            - { type: 'null' }
        rootRevisionId: { type: string }
        sequence: { type: integer, minimum: 1 }
        operation: { type: string, enum: [generate, edit, import] }
        assetId: { type: string }
        instruction: { type: string }
        preservation:
          type: object
          required: [subject, lettering, layout]
          properties:
            subject: { type: boolean }
            lettering: { type: boolean }
            layout: { type: boolean }
        quality: { type: string }
        ultraMaxMode: { type: string, enum: [default, quality, standard, lightning] }
        ultraMaxDetail: { type: string, enum: [low, medium, high, xhigh, max] }
        output:
          type: object
          required: [width, height, format, background, hasAlpha]
          properties:
            width: { type: integer, minimum: 1 }
            height: { type: integer, minimum: 1 }
            format: { type: string, enum: [png, webp, jpeg] }
            background: { type: string, enum: [transparent, opaque] }
            hasAlpha: { type: boolean }
        createdAt: { type: string, format: date-time }
    DesignRevisionResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data: { $ref: '#/components/schemas/DesignRevision' }
    DesignRevisionListResponse:
      type: object
      required: [success, data, meta]
      properties:
        success: { type: boolean, const: true }
        data:
          type: array
          items: { $ref: '#/components/schemas/DesignRevision' }
        meta:
          type: object
          required: [count, hasMore]
          properties:
            count: { type: integer, minimum: 0 }
            hasMore: { type: boolean }
            nextCursor: { type: string }
    DeveloperReference:
      type: object
      required: [assetId, role]
      properties:
        assetId: { type: string }
        role: { type: string, enum: [source, product, logo, style, layout, background] }
    PromptEnhancementRequest:
      type: object
      required: [projectId, operation, text]
      properties:
        projectId: { type: string }
        operation:
          { type: string, enum: [generate, generate_with_reference, screenshot_to_design, edit] }
        text: { type: string, maxLength: 30000 }
        mode: { type: string, enum: [preserve, auto, enhance], default: enhance }
        lockedText: { type: array, items: { type: string }, maxItems: 20 }
        references:
          { type: array, items: { $ref: '#/components/schemas/DeveloperReference' }, maxItems: 14 }
        output: { $ref: '#/components/schemas/DeveloperOutput' }
    GenerationRequest:
      type: object
      required: [projectId, operation, prompt]
      properties:
        projectId: { type: string }
        operation:
          { type: string, enum: [generate, generate_with_reference, screenshot_to_design, edit] }
        quality: { type: string, const: ultra-max }
        ultraMaxMode:
          { type: string, enum: [default, quality, standard, lightning], default: default }
        ultraMaxDetail: { type: string, enum: [low, medium, high, xhigh, max], default: medium }
        designId: { type: string, description: Required with sourceRevisionId for versioned edit }
        sourceRevisionId: { type: string, description: Required with designId for versioned edit }
        expectedHeadVersion: { type: integer, minimum: 0 }
        prompt:
          type: object
          required: [text]
          properties:
            text: { type: string, maxLength: 30000 }
            mode: { type: string, enum: [preserve, auto, enhance], default: preserve }
            lockedText: { type: array, items: { type: string }, maxItems: 20 }
        references:
          { type: array, items: { $ref: '#/components/schemas/DeveloperReference' }, maxItems: 14 }
        output: { $ref: '#/components/schemas/DeveloperOutput' }
        externalId: { type: string, maxLength: 160 }
    GenerationVectorizationRequest:
      type: object
      properties:
        projectId: { type: string }
        profile: { type: string, enum: [print, detail, simple], default: print }
        formats:
          type: array
          minItems: 1
          maxItems: 5
          items: { type: string, enum: [svg, pdf, png, eps, dxf] }
          default: [svg, pdf, png]
    SaveAssetToBrandLibraryRequest:
      type: object
      additionalProperties: false
      properties:
        brandId:
          type: string
          minLength: 1
          maxLength: 128
          description: Owned active Brand Library brand. Omit to use the active brand.
        description: { type: string, maxLength: 500 }
        tags:
          type: array
          maxItems: 20
          items: { type: string, minLength: 1, maxLength: 64 }
    SaveAssetToBrandLibraryResponse:
      type: object
      required: [success, data, library, meta]
      properties:
        success: { type: boolean, const: true }
        data:
          type: object
          description: Updated developer asset metadata, including its library link.
          additionalProperties: true
        library:
          type: object
          required: [brandId, assetId, type, name]
          properties:
            brandId: { type: string }
            assetId: { type: string }
            type: { type: string, const: reference }
            name: { type: string }
        meta:
          type: object
          required: [replayed]
          properties:
            replayed:
              type: boolean
              description: True when the same Brand Library import already existed.
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          default: false
        error:
          type: string
        code:
          type: string
        details:
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
            - type: string
            - type: 'null'
      required:
        - error

    DtfHalftoneRequest:
      type: object
      required:
        - imageUrl
      properties:
        imageUrl:
          type: string
          format: uri
          pattern: '^https://'
        dtfHalftone:
          type: integer
          minimum: 1
          maximum: 100
          default: 70
        dtfHalftoneAngle:
          type: number
          minimum: 0
          maximum: 360
          default: 22.5
        sessionId:
          type: string
          minLength: 1
          maxLength: 256

    DtfHalftoneMetadata:
      type: object
      required:
        - requestId
        - width
        - height
        - contentType
        - cacheHit
        - outputPath
        - dtfHalftone
        - dtfHalftoneAngle
      properties:
        requestId:
          type: string
        width:
          type: integer
        height:
          type: integer
        contentType:
          type: string
        cacheHit:
          type: boolean
        outputPath:
          type: string
        dtfHalftone:
          type: integer
        dtfHalftoneAngle:
          type: number

    DtfHalftoneSuccessResponse:
      type: object
      required:
        - success
        - imageUrl
        - processingType
        - tokenCost
        - metadata
      properties:
        success:
          type: boolean
          const: true
        imageUrl:
          type: string
          format: uri
        processingType:
          type: string
          const: dtf-halftone
        tokenCost:
          type: integer
          const: 10
        metadata:
          $ref: '#/components/schemas/DtfHalftoneMetadata'

    DesignAnalysisRequest:
      type: object
      additionalProperties: false
      required:
        - imageUrl
      properties:
        imageUrl:
          type: string
          format: uri
          pattern: '^https://'
        designPrompt:
          type: string
          maxLength: 4000
        printTarget:
          type: object
          additionalProperties: false
          required: [width, height, unit, method]
          description: Optional print-target control used only for rules-based measurements.
          properties:
            width: { type: number, exclusiveMinimum: 0, maximum: 72 }
            height: { type: number, exclusiveMinimum: 0, maximum: 72 }
            unit: { type: string, enum: [in, cm] }
            method:
              { type: string, enum: [dtf, dtg, screen_print, sublimation, embroidery, unspecified] }
        profile:
          type: string
          enum: [standard]
          default: standard
        metadata:
          type: object
          maxProperties: 20
          additionalProperties:
            oneOf:
              - { type: string, maxLength: 512 }
              - { type: number }
              - { type: boolean }
              - { type: 'null' }

    DesignRiskSummary:
      type: object
      properties:
        high:
          type: integer
        medium:
          type: integer
        low:
          type: integer

    DesignMockupAudience:
      type: object
      properties:
        ageBand:
          type: string
        genderPresentation:
          type: string
        audienceTags:
          type: array
          items:
            type: string
        stylePersona:
          type: string

    DesignMockupModelRecommendation:
      type: object
      properties:
        presentation:
          type: string
        ageBand:
          type: string
        vibe:
          type: string
        pose:
          type: string
        setting:
          type: string
        reasoning:
          type: string

    DesignMockupGarmentRecommendation:
      type: object
      properties:
        styleCode:
          type: string
          enum:
            - BC3001
            - '1717'
        brand:
          type: string
        colorKey:
          type: string
        displayColorName:
          type: string
        reasoning:
          type: string

    DesignAnalysisResponseBody:
      type: object
      properties:
        canonicalTitle:
          type: string
        filenameStem:
          type: string
        primarySubject:
          type: string
        secondarySubjects:
          type: array
          items:
            type: string
        styleTags:
          type: array
          items:
            type: string
        intendedUseGuess:
          type: string
        contrastAssessment:
          type: string
        assetTypeGuess:
          type: string
        transparencyDetected:
          type: boolean
        paletteHexes:
          type: array
          items:
            type: string
        detectedTextSnippets:
          type: array
          items:
            type: string
        detectedBrandTerms:
          type: array
          items:
            type: string
        riskSummary:
          $ref: '#/components/schemas/DesignRiskSummary'
        searchKeywords:
          type: array
          items:
            type: string
        searchSummary:
          type: string
        mockupAudience:
          $ref: '#/components/schemas/DesignMockupAudience'
        mockupModelRecommendation:
          $ref: '#/components/schemas/DesignMockupModelRecommendation'
        mockupGarmentRecommendation:
          $ref: '#/components/schemas/DesignMockupGarmentRecommendation'
        metadata:
          type: object
          additionalProperties: true

    DesignAnalysisSuccessResponse:
      type: object
      required:
        - success
        - data
        - analysis
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: '#/components/schemas/PublicArtworkAnalysisResponse'
        analysis:
          $ref: '#/components/schemas/DesignAnalysisResponseBody'
        meta:
          type: object
          required: [replayed]
          properties:
            replayed: { type: boolean }

    PublicArtworkAnalysisResponse:
      type: object
      required:
        [
          object,
          apiVersion,
          requestId,
          analysisVersion,
          profile,
          provenance,
          printTarget,
          printAssessment,
          facts,
          assessment,
        ]
      properties:
        object: { const: design_analysis }
        apiVersion: { const: v1 }
        requestId: { type: string }
        analysisVersion: { const: '1.0.0' }
        profile: { const: standard }
        provenance:
          type: object
          required: [source, provider, schemaVersion, promptVersion, measurementVersion]
          properties:
            source: { const: caller_supplied_https }
            provider: { const: managed_visual_analysis }
            schemaVersion: { type: string }
            promptVersion: { type: string }
            measurementVersion: { const: '2026-09-11.print-measurements.v1' }
        printTarget:
          oneOf:
            - type: 'null'
            - type: object
              required:
                [
                  width,
                  height,
                  unit,
                  method,
                  measurementVersion,
                  widthInches,
                  heightInches,
                  targetPixelsAt300Dpi,
                  effectiveDpi,
                ]
              properties:
                width: { type: number }
                height: { type: number }
                unit: { type: string, enum: [in, cm] }
                method:
                  {
                    type: string,
                    enum: [dtf, dtg, screen_print, sublimation, embroidery, unspecified],
                  }
                measurementVersion: { const: '2026-09-11.print-measurements.v1' }
                widthInches: { type: number }
                heightInches: { type: number }
                targetPixelsAt300Dpi:
                  type: object
                  properties: { width: { type: integer }, height: { type: integer } }
                effectiveDpi:
                  type: object
                  properties: { width: { type: number }, height: { type: number } }
        printAssessment:
          oneOf:
            - type: 'null'
            - type: object
              properties:
                source: { const: measurement_rule }
                uncertainty: { const: low }
                resolutionReadiness: { enum: [sufficient, review] }
        facts:
          type: object
          required: [mediaType, bytes, width, height, pixels, transparency]
          properties:
            mediaType: { enum: [image/png, image/jpeg, image/webp, image/svg+xml] }
            bytes: { type: integer }
            width: { type: integer }
            height: { type: integer }
            pixels: { type: integer }
            transparency:
              type: object
              properties:
                detected: { type: boolean }
                method: { const: decoded_alpha_statistics }
                confidence: { const: high }
        assessment:
          type: object
          description: Visual-model assessment; these fields are not deterministic facts.
          required:
            [
              source,
              uncertainty,
              canonicalTitle,
              filenameStem,
              primarySubject,
              secondarySubjects,
              styleTags,
              intendedUse,
              contrastAssessment,
              assetType,
              paletteHexes,
              detectedText,
              detectedBrandTerms,
              visualSearch,
              artworkDecisions,
            ]
          properties:
            source: { const: visual_assessment }
            uncertainty: { const: medium }
            canonicalTitle: { type: string }
            filenameStem: { type: string }
            primarySubject: { type: string }
            secondarySubjects: { type: array, items: { type: string } }
            styleTags: { type: array, items: { type: string } }
            intendedUse: { type: string }
            contrastAssessment: { type: string }
            assetType: { type: string }
            paletteHexes: { type: array, items: { type: string } }
            detectedText: { type: array, items: { type: string } }
            detectedBrandTerms: { type: array, items: { type: string } }
            visualSearch:
              type: object
              properties:
                { keywords: { type: array, items: { type: string } }, summary: { type: string } }
            artworkDecisions:
              type: object
              properties:
                riskSummary: { $ref: '#/components/schemas/DesignRiskSummary' }
                mockupAudience: { $ref: '#/components/schemas/DesignMockupAudience' }
                mockupModelRecommendation:
                  { $ref: '#/components/schemas/DesignMockupModelRecommendation' }
                mockupGarmentRecommendation:
                  { $ref: '#/components/schemas/DesignMockupGarmentRecommendation' }

    RemoveBackgroundOptions:
      type: object
      properties:
        interiorMode:
          type: string
          enum: [auto, solid, detailed]
          default: auto
          description: |
            Mask topology inside the design. `auto`: leak pockets reached only
            through narrow gaps stay filled; genuine holes are removed.
            `solid`: sticker-style — only the open outer field is removed.
            `detailed`: every background-colored region connected to the
            border is removed.
        enclosedPolicy:
          type: string
          enum: [auto, remove, keep]
          default: auto
          description: |
            Fully enclosed background-colored regions (letter counters, holes).
            `auto`: punch crisp-ringed holes and near-exact-background
            plateaus whose enclosing ink sits near open background; keep
            gradient-ringed shading pockets and regions buried deep in design
            mass. `remove`: always punch. `keep`: never touch.
        keepPoints:
          type: array
          maxItems: 50
          items:
            type: array
            minItems: 2
            maxItems: 2
            items:
              type: number
              minimum: 0
              maximum: 1
          description: |
            Normalized [x, y] points (0-1 relative to image size). The
            enclosed region under each point stays filled, overriding the
            heuristic and enclosedPolicy. For authorial intent no geometry
            can infer (outline lettering meant to keep its fill). Keep wins
            over punch on the same region.
        punchPoints:
          type: array
          maxItems: 50
          items:
            type: array
            minItems: 2
            maxItems: 2
            items:
              type: number
              minimum: 0
              maximum: 1
          description: |
            Normalized [x, y] points (0-1). The enclosed region under each
            point is removed, even under enclosedPolicy keep.
        bgColorHint:
          type: array
          minItems: 3
          maxItems: 3
          items:
            type: integer
            minimum: 0
            maximum: 255
          description: |
            Explicit background color `[r, g, b]` for finishing a cut-out
            image. Already-transparent inputs normally pass through; with a
            hint they are processed — existing transparency is treated as
            background and leftover interior patches matching the hint are
            removed. Existing transparency is always preserved. Skips the
            flatness router.
        quality:
          type: string
          enum: [high, fast]
          default: high
          description: |
            `high` supersamples the mask 2x for sub-pixel anti-aliasing
            (~4x compute, auto-skipped above 6MP). `fast` runs at native
            resolution.
        tolerance:
          type: number
          minimum: 1
          maximum: 40
          description: |
            ΔE (CIELAB) background color tolerance. When omitted, derived
            adaptively from measured border noise (typically 8–16).

    RemoveBackgroundRequest:
      type: object
      description: Provide exactly one of `imageUrl` or `imageData`.
      properties:
        imageUrl:
          type: string
          format: uri
          pattern: '^https://'
          description: HTTPS URL to the source image
        imageData:
          type: string
          description: Base64 image data URL (`data:image/png;base64,...`)
        options:
          $ref: '#/components/schemas/RemoveBackgroundOptions'

    RemoveBackgroundQaReport:
      type: object
      required:
        - route
        - reasons
      properties:
        route:
          type: string
          enum: [native, skipped_already_transparent, fallback_recommended]
        reasons:
          type: array
          items:
            type: string
          description: Human-readable route decisions and warnings
        flatness:
          type: number
          description: Border background uniformity (router input)
        borderCoverage:
          type: number
          description: Fraction of border within tolerance of the detected background
        removedFraction:
          type: number
          description: Fraction of all pixels made transparent
        enclosedRemovedFraction:
          type: number
          description: Fraction removed as enclosed holes (letter counters etc.)
        pocketsFilledFraction:
          type: number
          description: Fraction refilled as leak pockets by interior reconstruction
        fgComponentCount:
          type: integer
          description: Connected foreground components after cleanup
        specksRemoved:
          type: integer
          description: Low-contrast noise specks deleted
        durationMs:
          type: integer

    RemoveBackgroundSuccessResponse:
      type: object
      required:
        - success
        - model
        - imageUrl
        - route
        - qa
      properties:
        success:
          type: boolean
          const: true
        model:
          type: string
          const: DG-R-BG-01
          description: Identifier of the background-removal model that processed the request
        requestId:
          type: string
          description: Stable result handle — use it to submit feedback via /v1/remove-background/feedback
        imageUrl:
          type: string
          description: Output PNG with alpha (echoes the input for `skipped_already_transparent`)
        width:
          type: integer
        height:
          type: integer
        route:
          type: string
          enum: [native, skipped_already_transparent]
        qa:
          $ref: '#/components/schemas/RemoveBackgroundQaReport'
        tokenCost:
          type: number
          description: Tokens charged (`0` for already-transparent passthrough)

    RemoveBackgroundDeclinedResponse:
      type: object
      required:
        - success
        - error
        - code
        - qa
      properties:
        success:
          type: boolean
          const: false
        error:
          type: string
        code:
          type: string
          const: not_processable
        model:
          type: string
          const: DG-R-BG-01
        requestId:
          type: string
          description: Result handle — declined results accept feedback too
        route:
          type: string
          const: fallback_recommended
        qa:
          $ref: '#/components/schemas/RemoveBackgroundQaReport'

    RemoveBackgroundFeedbackRequest:
      type: object
      required:
        - requestId
      description: requestId plus at least one of verdict, rating, issues, comment, expected.
      properties:
        requestId:
          type: string
        verdict:
          type: string
          enum: [up, down]
        rating:
          type: object
          required: [value, scale]
          properties:
            value:
              type: integer
              minimum: 1
            scale:
              type: integer
              enum: [5, 7, 10]
        issues:
          type: array
          maxItems: 10
          items:
            type: string
            maxLength: 64
          description: Structured issue tags, e.g. background_left, design_removed, edges_jagged
        comment:
          type: string
          maxLength: 2000
          description: What's wrong with this result
        expected:
          type: string
          maxLength: 2000
          description: What the result should have been

    RemoveBackgroundFeedbackResponse:
      type: object
      required:
        - success
        - feedbackId
        - requestId
      properties:
        success:
          type: boolean
          const: true
        feedbackId:
          type: string
        requestId:
          type: string

    EditorSessionCreateRequest:
      type: object
      description: Provide imageUrl or imageData, plus default processing options for the session.
      properties:
        imageUrl:
          type: string
          format: uri
        imageData:
          type: string
          description: base64 image data URL
        options:
          type: object
          description: Default DG-R-BG-01 options applied to every run in this session
          additionalProperties: true

    EditorSessionCreateResponse:
      type: object
      required: [success, editorSessionId, editorUrl, editorToken, expiresAt]
      properties:
        success:
          type: boolean
          const: true
        editorSessionId:
          type: string
        editorUrl:
          type: string
          format: uri
          description: Hosted editor URL to send the end-user to (token included)
        editorToken:
          type: string
          description: Capability token — the end-user credential. Store server-side only.
        expiresAt:
          type: string
          format: date-time

    EditorSessionState:
      type: object
      required: [id, status, runCount, maxRuns]
      properties:
        id:
          type: string
        status:
          type: string
          enum: [open, completed, expired]
        inputUrl:
          type: string
        options:
          type: object
          additionalProperties: true
        runCount:
          type: integer
        maxRuns:
          type: integer
        latestResult:
          type: object
          nullable: true
          properties:
            requestId:
              type: string
            imageUrl:
              type: string
              nullable: true
            route:
              type: string
            keepPoints:
              type: array
              items:
                type: array
                items:
                  type: number
            punchPoints:
              type: array
              items:
                type: array
                items:
                  type: number
        createdAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
          nullable: true

    EditorSessionStateResponse:
      type: object
      required: [success, session]
      properties:
        success:
          type: boolean
          const: true
        session:
          $ref: '#/components/schemas/EditorSessionState'

    EditorProcessRequest:
      type: object
      required: [token]
      properties:
        token:
          type: string
        keepPoints:
          type: array
          maxItems: 50
          items:
            type: array
            minItems: 2
            maxItems: 2
            items:
              type: number
              minimum: 0
              maximum: 1
        punchPoints:
          type: array
          maxItems: 50
          items:
            type: array
            minItems: 2
            maxItems: 2
            items:
              type: number
              minimum: 0
              maximum: 1
        enclosedPolicy:
          type: string
          enum: [auto, remove, keep]

    EditorProcessResponse:
      type: object
      required: [success, model, requestId, route, qa]
      properties:
        success:
          type: boolean
          const: true
        model:
          type: string
          const: DG-R-BG-01
        requestId:
          type: string
        imageUrl:
          type: string
          nullable: true
        width:
          type: integer
        height:
          type: integer
        route:
          type: string
        qa:
          $ref: '#/components/schemas/RemoveBackgroundQaReport'
        runsRemaining:
          type: integer

    CreateApiKeyRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 64
        scopes:
          type: array
          maxItems: 20
          items:
            oneOf:
              - type: string
                enum:
                  - dtf_halftone
                  - design_analysis
                  - usage_read
                  - files_read
                  - '*'
              - type: string
                pattern: '^[a-z][a-z0-9_:-]*$'

    ApiKey:
      type: object
      required:
        - id
        - name
        - keyPrefix
        - last4
        - scopes
      properties:
        id:
          type: string
        name:
          type: string
        keyPrefix:
          type: string
        last4:
          type: string
        scopes:
          type: array
          items:
            type: string
        status:
          type: string
          enum: [active, revoked]
        usageCount:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        lastUsedAt:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
        revokedAt:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'

    ListApiKeysResponse:
      type: object
      required:
        - success
        - keys
        - availableScopes
      properties:
        success:
          type: boolean
          const: true
        keys:
          type: array
          items:
            $ref: '#/components/schemas/ApiKey'
        availableScopes:
          type: array
          items:
            type: string
            enum:
              - dtf_halftone
              - usage_read
              - files_read
              - '*'

    CreateApiKeyResponse:
      type: object
      required:
        - success
        - apiKey
        - key
        - availableScopes
      properties:
        success:
          type: boolean
          const: true
        apiKey:
          type: string
        availableScopes:
          type: array
          items:
            type: string
            enum:
              - dtf_halftone
              - usage_read
              - files_read
              - '*'
        key:
          type: object
          required:
            - id
            - name
            - keyPrefix
            - last4
            - scopes
            - createdAt
          properties:
            id:
              type: string
            name:
              type: string
            keyPrefix:
              type: string
            last4:
              type: string
            scopes:
              type: array
              items:
                type: string
            createdAt:
              type: string
              format: date-time
        note:
          type: string

    UsageItem:
      type: object
      required:
        - id
        - userId
        - apiKeyId
        - route
        - method
        - statusCode
        - success
        - latencyMs
        - timestamp
      properties:
        id:
          type: string
        userId:
          type: string
        apiKeyId:
          type: string
        route:
          type: string
        method:
          type: string
        statusCode:
          type: integer
        success:
          type: boolean
        latencyMs:
          type: number
        operationType:
          oneOf:
            - type: string
            - type: 'null'
        tokenCost:
          oneOf:
            - type: number
            - type: 'null'
        requestId:
          oneOf:
            - type: string
            - type: 'null'
        outputUrl:
          oneOf:
            - type: string
              format: uri
            - type: 'null'
        metadata:
          oneOf:
            - type: object
              additionalProperties: true
            - type: 'null'
        timestamp:
          type: string
          format: date-time

    UsagePagination:
      type: object
      required:
        - limit
        - offset
        - hasMore
        - nextOffset
      properties:
        limit:
          type: integer
        offset:
          type: integer
        page:
          oneOf:
            - type: integer
            - type: 'null'
        pageCount:
          oneOf:
            - type: integer
            - type: 'null'
        hasMore:
          type: boolean
        nextOffset:
          oneOf:
            - type: integer
            - type: 'null'

    OperatorUsageRecord:
      type: object
      required: [runId, operationType, credits, status, createdAt]
      properties:
        runId: { type: string }
        operationType: { type: string }
        credits: { type: integer, minimum: 0 }
        status: { type: string, enum: [reserved, settled, released] }
        createdAt: { type: string, format: date-time }

    OperatorUsageSummary:
      type: object
      required: [settledCredits, reservedCredits, releasedCredits, counts]
      properties:
        settledCredits: { type: integer, minimum: 0, description: Credits actually spent. }
        reservedCredits:
          { type: integer, minimum: 0, description: Current in-flight exposure; not final spend. }
        releasedCredits:
          { type: integer, minimum: 0, description: Credits released or refunded after an attempt. }
        counts:
          type: object
          required: [settled, reserved, released]
          properties:
            settled: { type: integer, minimum: 0 }
            reserved: { type: integer, minimum: 0 }
            released: { type: integer, minimum: 0 }

    OperatorUsage:
      type: object
      required: [records, summary, pagination]
      properties:
        records:
          type: array
          items: { $ref: '#/components/schemas/OperatorUsageRecord' }
        summary: { $ref: '#/components/schemas/OperatorUsageSummary' }
        pagination:
          type: object
          required: [limit, offset, total, hasMore, nextOffset]
          properties:
            limit: { type: integer }
            offset: { type: integer }
            total: { type: integer }
            hasMore: { type: boolean }
            nextOffset:
              oneOf:
                - type: integer
                - type: 'null'

    ListUsageResponse:
      type: object
      required:
        - success
        - usage
        - count
        - total
        - pagination
        - operatorUsage
      properties:
        success:
          type: boolean
          const: true
        usage:
          type: array
          items:
            $ref: '#/components/schemas/UsageItem'
        count:
          type: integer
        total:
          type: integer
        pagination:
          $ref: '#/components/schemas/UsagePagination'
        operatorUsage:
          $ref: '#/components/schemas/OperatorUsage'
        filters:
          type: object
          properties:
            apiKeyId:
              oneOf:
                - type: string
                - type: 'null'
            startDate:
              oneOf:
                - type: string
                  format: date-time
                - type: 'null'
            endDate:
              oneOf:
                - type: string
                  format: date-time
                - type: 'null'
