openapi: 3.1.0
info:
  title: nocensor.ai API
  version: "1"
  description: "AI image and video generation API. All endpoints require a Bearer
    API key (`Authorization: Bearer <key>`). Keys are issued from the
    [developers dashboard](https://nocensor.ai/developers)."
servers:
  - url: https://nocensor.ai
    description: Production
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: "API key issued from the developers dashboard. Send as
        `Authorization: Bearer <key>`."
  schemas:
    CreateWebhookRequest:
      type: object
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum: &a1
              - job.completed
              - job.failed
              - job.cancelled
              - payment.completed
              - lora.training_completed
              - lora.training_failed
              - pipeline.completed
              - webhook.test
          minItems: 1
          maxItems: 8
      required:
        - url
        - events
    UpdateWebhookRequest:
      type: object
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum: *a1
          minItems: 1
          maxItems: 8
        is_active:
          type: boolean
    EnhanceRequest:
      oneOf:
        - type: object
          properties:
            operation:
              type: string
              enum:
                - upscale
            source:
              type: string
            scale:
              type: integer
              minimum: 2
              maximum: 4
              default: 2
          required:
            - operation
            - source
        - type: object
          properties:
            operation:
              type: string
              enum:
                - face-restore
            source:
              type: string
          required:
            - operation
            - source
        - type: object
          properties:
            operation:
              type: string
              enum:
                - bg-replace
            source:
              type: string
            background_prompt:
              type: string
              minLength: 1
              maxLength: 2000
          required:
            - operation
            - source
            - background_prompt
        - type: object
          properties:
            operation:
              type: string
              enum:
                - attach-object
            source:
              type: string
            object_prompt:
              type: string
              minLength: 1
              maxLength: 2000
            mask:
              type: string
          required:
            - operation
            - source
            - object_prompt
            - mask
    UndressRequest:
      type: object
      properties:
        source:
          type: string
        biometric_consent:
          type: boolean
          enum:
            - true
        real_person_consent:
          type: boolean
          enum:
            - true
      required:
        - source
        - biometric_consent
        - real_person_consent
    PipelineRequest:
      type: object
      properties:
        stages:
          type: array
          items:
            oneOf:
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - generate
                  prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                  negative_prompt:
                    type: string
                    maxLength: 2000
                  model:
                    type: string
                  loras:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        strength:
                          type: number
                          minimum: 0
                          maximum: 2
                      required:
                        - id
                        - strength
                    maxItems: 2
                  width:
                    type: integer
                  height:
                    type: integer
                  seed:
                    type: integer
                required:
                  - op
                  - prompt
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - undress
                  biometric_consent:
                    type: boolean
                    enum:
                      - true
                  real_person_consent:
                    type: boolean
                    enum:
                      - true
                required:
                  - op
                  - biometric_consent
                  - real_person_consent
                description: Admin API keys only; returns 503 for non-admin keys (BUG-1156).
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - undress-v2
                  biometric_consent:
                    type: boolean
                    enum:
                      - true
                  real_person_consent:
                    type: boolean
                    enum:
                      - true
                required:
                  - op
                  - biometric_consent
                  - real_person_consent
                description: Admin API keys only; returns 503 for non-admin keys (BUG-1156).
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - undress-v3
                  biometric_consent:
                    type: boolean
                    enum:
                      - true
                  real_person_consent:
                    type: boolean
                    enum:
                      - true
                required:
                  - op
                  - biometric_consent
                  - real_person_consent
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - face-swap
                  biometric_consent:
                    type: boolean
                    enum:
                      - true
                  face_model_id:
                    type: string
                    format: uuid
                  face:
                    type: string
                required:
                  - op
                  - biometric_consent
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - upscale
                  scale:
                    type: integer
                    minimum: 2
                    maximum: 4
                    default: 2
                required:
                  - op
                description: Admin API keys only; returns 403 for non-admin keys.
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - face-restore
                required:
                  - op
                description: Admin API keys only; returns 403 for non-admin keys.
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - fix-hand
                  strength:
                    type: integer
                    minimum: 0
                    maximum: 5
                required:
                  - op
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - bg-replace
                  background_prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                required:
                  - op
                  - background_prompt
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - attach-object
                  object_prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                  mask:
                    type: string
                required:
                  - op
                  - object_prompt
                  - mask
                description: Admin API keys only; returns 403 for non-admin keys.
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - attach-object-v2
                  object_prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                  mask:
                    type: string
                  prompt:
                    type: string
                    maxLength: 2000
                  seed:
                    type: integer
                required:
                  - op
                  - object_prompt
                  - mask
                description: Admin API keys only; returns 403 for non-admin keys.
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - attach-object-v3
                  object_prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                  mask:
                    type: string
                  reference:
                    type: string
                  prompt:
                    type: string
                    maxLength: 2000
                  seed:
                    type: integer
                required:
                  - op
                  - object_prompt
                  - mask
                description: Admin API keys only; returns 403 for non-admin keys.
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - animate
                  motion_prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                  frames:
                    type: integer
                required:
                  - op
                  - motion_prompt
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - redress
                  biometric_consent:
                    type: boolean
                    enum:
                      - true
                  clothing_prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                required:
                  - op
                  - biometric_consent
                  - clothing_prompt
                description: Admin API keys only; returns 403 for non-admin keys.
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - redress-v2
                  biometric_consent:
                    type: boolean
                    enum:
                      - true
                  clothing_prompt:
                    type: string
                    minLength: 1
                    maxLength: 2000
                required:
                  - op
                  - biometric_consent
                  - clothing_prompt
              - type: object
                properties:
                  op:
                    type: string
                    enum:
                      - redress-vton
                  biometric_consent:
                    type: boolean
                    enum:
                      - true
                  garment:
                    type: string
                    minLength: 1
                required:
                  - op
                  - biometric_consent
                  - garment
          minItems: 1
          maxItems: 5
        source:
          type: string
        webhook_event_id_prefix:
          type: string
          maxLength: 64
      required:
        - stages
    GenerateRequest:
      type: object
      properties:
        prompt:
          type: string
          minLength: 1
          maxLength: 2000
        negativePrompt:
          type: string
          maxLength: 2000
        model:
          type: string
          enum: &a2
            - realistic
            - photoreal-plus
            - anime
            - hentai
            - stylized
            - chroma
            - z-image-turbo
          default: realistic
        seed:
          type: integer
          minimum: 0
        width:
          type: integer
          minimum: 64
          maximum: 1280
        height:
          type: integer
          minimum: 64
          maximum: 1280
        image:
          type: string
        denoise:
          type: number
          minimum: 0.1
          maximum: 1
        loras:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              strength:
                type: number
                minimum: 0
                maximum: 2
            required:
              - id
          maxItems: 2
        characterId:
          type: string
          format: uuid
      required:
        - prompt
    VideoRequest:
      type: object
      properties:
        prompt:
          type: string
          minLength: 1
          maxLength: 2000
        negativePrompt:
          type: string
          maxLength: 2000
        mode:
          type: string
          enum: &a3
            - t2v
            - i2v
          default: t2v
        model:
          type: string
          enum: &a4
            - wan-remix
            - phr00t-v10
            - wan-21
        image:
          type: string
        duration:
          type: string
          enum: &a5
            - short
            - medium
            - long
            - long+
            - extra-long
          default: short
        resolution:
          type: string
          enum: &a6
            - standard
            - hd
          default: standard
        width:
          type: integer
          minimum: 256
          maximum: 1280
        height:
          type: integer
          minimum: 256
          maximum: 1280
        seed:
          type: integer
          minimum: 0
        biometricConsent:
          type: boolean
        loras:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              strength:
                type: number
                minimum: 0
                maximum: 2
            required:
              - id
          maxItems: 1
        noiseAugStrength:
          type: number
          minimum: 0
          maximum: 1
        audioMode:
          type: string
          enum: &a7
            - none
            - sfx
            - music
            - voice
            - moaning
        audioPrompt:
          type: string
          maxLength: 500
      required:
        - prompt
    FaceSwapRequest:
      type: object
      properties:
        sourceImage:
          type: string
          minLength: 1
        targetImage:
          type: string
          minLength: 1
        biometricConsent:
          type: boolean
          enum:
            - true
        faceRestoration:
          type: boolean
          default: true
      required:
        - sourceImage
        - targetImage
        - biometricConsent
    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - ok
        version:
          type: string
        timestamp:
          type: string
          format: date-time
      required:
        - status
        - version
        - timestamp
    Meta:
      type: object
      properties:
        request_id:
          type: string
      required:
        - request_id
      additionalProperties: {}
    AccountResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            credits_balance:
              type: integer
            usage:
              type: object
              properties:
                total_jobs:
                  type: integer
                completed_jobs:
                  type: integer
              required:
                - total_jobs
                - completed_jobs
          required:
            - credits_balance
            - usage
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    ErrorObject:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        status:
          type: integer
        request_id:
          type: string
      required:
        - code
        - message
        - status
        - request_id
    ErrorResponse:
      type: object
      properties:
        error:
          $ref: "#/components/schemas/ErrorObject"
      required:
        - error
    CreditsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            balance:
              type: integer
            lifetime_purchased:
              type: integer
            lifetime_consumed:
              type: integer
          required:
            - balance
            - lifetime_purchased
            - lifetime_consumed
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    PaymentsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            items:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  gateway:
                    type: string
                  amount_usd:
                    type: number
                  credits:
                    type: integer
                  status:
                    type: string
                  created_at:
                    type: string
                    format: date-time
                required:
                  - id
                  - gateway
                  - amount_usd
                  - credits
                  - status
                  - created_at
          required:
            - items
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    CharactersResponse:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              displayName:
                type: string
              description:
                type:
                  - string
                  - "null"
              tags:
                type: array
                items:
                  type: string
              isUnlocked:
                type: boolean
              unlockThreshold:
                type: integer
              unlockProgress:
                type: integer
              isPremium:
                type: boolean
            required:
              - id
              - displayName
              - description
              - tags
              - isUnlocked
              - unlockThreshold
              - unlockProgress
              - isPremium
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    ModelsCatalogResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            checkpoints:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                required:
                  - id
                  - name
            system_loras:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  category:
                    type: string
                  trigger_word:
                    type:
                      - string
                      - "null"
                required:
                  - id
                  - name
                  - category
                  - trigger_word
            operations:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  label:
                    type: string
                  modality:
                    type: string
                required:
                  - id
                  - label
                  - modality
          required:
            - checkpoints
            - system_loras
            - operations
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    GenerateAcceptedResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
              enum:
                - pending
            created_at:
              type: string
              format: date-time
          required:
            - id
            - status
            - created_at
        meta:
          type: object
          properties:
            request_id:
              type: string
            credits_remaining:
              type: integer
            poll_url:
              type: string
            estimated_seconds:
              type: integer
          required:
            - request_id
            - poll_url
            - estimated_seconds
      required:
        - data
        - meta
    VideoAcceptedResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
              enum:
                - pending
            created_at:
              type: string
              format: date-time
          required:
            - id
            - status
            - created_at
        meta:
          type: object
          properties:
            request_id:
              type: string
            credits_remaining:
              type: integer
            poll_url:
              type: string
            estimated_seconds:
              type: integer
            duration:
              type: string
              enum:
                - short
                - medium
                - long
                - long+
                - extra-long
            resolution:
              type: string
              enum:
                - standard
                - hd
          required:
            - request_id
            - poll_url
            - estimated_seconds
            - duration
            - resolution
      required:
        - data
        - meta
    UndressAcceptedResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
              enum:
                - pending
            created_at:
              type: string
              format: date-time
          required:
            - id
            - status
            - created_at
        meta:
          type: object
          properties:
            request_id:
              type: string
            credits_remaining:
              type: integer
            poll_url:
              type: string
          required:
            - request_id
            - poll_url
      required:
        - data
        - meta
    FaceSwapAcceptedResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
              enum:
                - pending
            created_at:
              type: string
              format: date-time
          required:
            - id
            - status
            - created_at
        meta:
          type: object
          properties:
            request_id:
              type: string
            credits_remaining:
              type: integer
            poll_url:
              type: string
            estimated_seconds:
              type: integer
          required:
            - request_id
            - poll_url
            - estimated_seconds
      required:
        - data
        - meta
    EnhanceAcceptedResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
              enum:
                - pending
            created_at:
              type: string
              format: date-time
          required:
            - id
            - status
            - created_at
        meta:
          type: object
          properties:
            request_id:
              type: string
            credits_remaining:
              type: integer
            poll_url:
              type: string
          required:
            - request_id
            - poll_url
      required:
        - data
        - meta
    PipelineDryRunResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            stages:
              type: array
              items:
                type: object
                properties:
                  op:
                    type: string
                  credit_cost:
                    type: number
                required:
                  - op
                  - credit_cost
            total_cost:
              type: number
          required:
            - stages
            - total_cost
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    PipelineCreatedResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
              enum:
                - pending
            stages:
              type: array
              items:
                type: object
                properties:
                  op:
                    type: string
                  job_id:
                    type:
                      - string
                      - "null"
                  status:
                    type: string
                    enum:
                      - pending
                  stage_index:
                    type: integer
                required:
                  - op
                  - job_id
                  - status
                  - stage_index
          required:
            - id
            - status
            - stages
        meta:
          type: object
          properties:
            request_id:
              type: string
            credits_remaining:
              type: integer
            credits_charged:
              type: integer
            poll_url:
              type: string
          required:
            - request_id
            - credits_charged
            - poll_url
      required:
        - data
        - meta
    PipelineStatusResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
            stages:
              type: array
              items:
                type: object
                properties:
                  op:
                    type: string
                  job_id:
                    type: string
                  status:
                    type: string
                  stage_index:
                    type: integer
                  error_message:
                    type:
                      - string
                      - "null"
                required:
                  - op
                  - job_id
                  - status
                  - stage_index
                  - error_message
          required:
            - id
            - status
            - stages
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    JobListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              status:
                type: string
              workflow:
                type: string
              credits_charged:
                type: number
              created_at:
                type:
                  - string
                  - "null"
              completed_at:
                type: string
              error:
                type: object
                properties:
                  message:
                    type: string
                required:
                  - message
            required:
              - id
              - status
              - workflow
              - credits_charged
              - created_at
        meta:
          type: object
          properties:
            request_id:
              type: string
            page:
              type: integer
            page_size:
              type: integer
            total:
              type: integer
            total_pages:
              type: integer
          required:
            - request_id
            - page
            - page_size
            - total
            - total_pages
      required:
        - data
        - meta
    JobDetailResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
            workflow:
              type: string
            credits_charged:
              type: number
            created_at:
              type:
                - string
                - "null"
            completed_at:
              type:
                - string
                - "null"
            outputs:
              type: array
              items:
                type: object
                properties:
                  url:
                    type:
                      - string
                      - "null"
                  media_type:
                    type: string
                  size_bytes:
                    type:
                      - number
                      - "null"
                  expires_at:
                    type: string
                required:
                  - url
                  - media_type
                  - size_bytes
                  - expires_at
            error:
              type: object
              properties:
                message:
                  type: string
              required:
                - message
            poll_interval_seconds:
              type: integer
          required:
            - id
            - status
            - workflow
            - credits_charged
            - created_at
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    JobCancelResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
              enum:
                - cancelled
          required:
            - id
            - status
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    WebhookListResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            items:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  url:
                    type: string
                  events:
                    type: array
                    items:
                      type: string
                  is_active:
                    type: boolean
                  created_at:
                    type: string
                  last_delivery_at:
                    type:
                      - string
                      - "null"
                  last_success_at:
                    type:
                      - string
                      - "null"
                  last_failure_at:
                    type:
                      - string
                      - "null"
                required:
                  - id
                  - url
                  - events
                  - is_active
                  - created_at
                  - last_delivery_at
                  - last_success_at
                  - last_failure_at
          required:
            - items
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    WebhookCreateResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            url:
              type: string
            events:
              type: array
              items:
                type: string
            is_active:
              type: boolean
            created_at:
              type: string
            secret:
              type: string
              description: Signing secret. Returned ONCE, here, and never again — no endpoint
                can retrieve it later. Store it immediately; if lost, rotate via
                PATCH /webhooks/{id}?rotate_secret=true.
          required:
            - id
            - url
            - events
            - is_active
            - created_at
            - secret
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    WebhookGetResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            url:
              type: string
            events:
              type: array
              items:
                type: string
            is_active:
              type: boolean
            created_at:
              type: string
            last_delivery_at:
              type:
                - string
                - "null"
            last_success_at:
              type:
                - string
                - "null"
            last_failure_at:
              type:
                - string
                - "null"
          required:
            - id
            - url
            - events
            - is_active
            - created_at
            - last_delivery_at
            - last_success_at
            - last_failure_at
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    WebhookUpdateResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            url:
              type: string
            events:
              type: array
              items:
                type: string
            is_active:
              type: boolean
            created_at:
              type: string
            secret:
              type: string
              description: Present ONLY when ?rotate_secret=true was passed. When present this
                is the one and only time the new secret is returned — store it
                immediately.
          required:
            - id
            - url
            - events
            - is_active
            - created_at
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    WebhookDeleteResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
            deleted:
              type: boolean
              enum:
                - true
          required:
            - id
            - deleted
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    WebhookDeliveryListResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            items:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  event_id:
                    type: string
                  event_type:
                    type: string
                  status:
                    type: string
                  attempts:
                    type: number
                  last_response_status:
                    type:
                      - number
                      - "null"
                  last_error:
                    type:
                      - string
                      - "null"
                  created_at:
                    type: string
                  delivered_at:
                    type:
                      - string
                      - "null"
                required:
                  - id
                  - event_id
                  - event_type
                  - status
                  - attempts
                  - last_response_status
                  - last_error
                  - created_at
                  - delivered_at
          required:
            - items
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
    WebhookTestResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            delivery_id:
              type: string
            status:
              type: string
              enum:
                - queued
          required:
            - delivery_id
            - status
        meta:
          $ref: "#/components/schemas/Meta"
      required:
        - data
        - meta
  parameters: {}
paths:
  /api/v1/health:
    get:
      operationId: getHealth
      summary: Health check
      tags:
        - system
      responses:
        "200":
          description: Service is healthy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /api/v1/account:
    get:
      operationId: getAccount
      summary: Get account details
      tags:
        - account
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Account details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Account not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/credits:
    get:
      operationId: getCredits
      summary: Get credit balance
      tags:
        - account
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Credit balance details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditsResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: No credit balance found for the authenticated account.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/payments:
    get:
      operationId: getPayments
      summary: List payment history
      tags:
        - account
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Payment history.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentsResponse"
        "400":
          description: Bad request — malformed pagination cursor (`before` query param is
            not a valid datetime).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/characters:
    get:
      operationId: getCharacters
      summary: List available characters
      tags:
        - account
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Characters catalog.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CharactersResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/models:
    get:
      operationId: getModels
      summary: List available models
      tags:
        - account
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Models catalog.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelsCatalogResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/generate:
    post:
      operationId: generate
      summary: Generate an image
      tags:
        - generation
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                prompt:
                  type: string
                  minLength: 1
                  maxLength: 2000
                negativePrompt:
                  type: string
                  maxLength: 2000
                model:
                  type: string
                  enum: *a2
                  default: realistic
                seed:
                  type: integer
                  minimum: 0
                width:
                  type: integer
                  minimum: 64
                  maximum: 1280
                height:
                  type: integer
                  minimum: 64
                  maximum: 1280
                image:
                  type: string
                denoise:
                  type: number
                  minimum: 0.1
                  maximum: 1
                loras:
                  type: array
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      strength:
                        type: number
                        minimum: 0
                        maximum: 2
                    required:
                      - id
                  maxItems: 2
                characterId:
                  type: string
                  format: uuid
              required:
                - prompt
      responses:
        "202":
          description: Job accepted and queued for processing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GenerateAcceptedResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          description: Insufficient credits.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: A referenced LoRA does not exist (`LORA_NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict — a referenced LoRA is not ready (`LORA_NOT_READY`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Payload too large — a supplied image exceeds the size limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Validation error — a field failed schema validation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: GPU capacity unavailable — retry shortly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/video:
    post:
      operationId: generateVideo
      summary: Generate a video
      tags:
        - generation
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                prompt:
                  type: string
                  minLength: 1
                  maxLength: 2000
                negativePrompt:
                  type: string
                  maxLength: 2000
                mode:
                  type: string
                  enum: *a3
                  default: t2v
                model:
                  type: string
                  enum: *a4
                image:
                  type: string
                duration:
                  type: string
                  enum: *a5
                  default: short
                resolution:
                  type: string
                  enum: *a6
                  default: standard
                width:
                  type: integer
                  minimum: 256
                  maximum: 1280
                height:
                  type: integer
                  minimum: 256
                  maximum: 1280
                seed:
                  type: integer
                  minimum: 0
                biometricConsent:
                  type: boolean
                loras:
                  type: array
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      strength:
                        type: number
                        minimum: 0
                        maximum: 2
                    required:
                      - id
                  maxItems: 1
                noiseAugStrength:
                  type: number
                  minimum: 0
                  maximum: 1
                audioMode:
                  type: string
                  enum: *a7
                audioPrompt:
                  type: string
                  maxLength: 500
              required:
                - prompt
      responses:
        "202":
          description: Job accepted and queued for processing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VideoAcceptedResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          description: Insufficient credits.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: A referenced LoRA does not exist (`LORA_NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict — a referenced LoRA is not ready (`LORA_NOT_READY`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Payload too large — a supplied image exceeds the size limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Validation error — a field failed schema validation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: GPU capacity unavailable — retry shortly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/undress:
    post:
      operationId: undress
      summary: AI undress
      tags:
        - generation
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                source:
                  type: string
                biometric_consent:
                  type: boolean
                  enum:
                    - true
                real_person_consent:
                  type: boolean
                  enum:
                    - true
              required:
                - source
                - biometric_consent
                - real_person_consent
      responses:
        "202":
          description: Job accepted and queued for processing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UndressAcceptedResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          description: Insufficient credits.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: The source job does not exist, or it has no stored output
            (`SOURCE_NOT_FOUND` in both cases).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — route-level geo-block
            (`REGION_BLOCKED`, `application/json` envelope) or middleware
            geo-block (plain text `Unavailable in your region`,
            production-only).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: 'Service temporarily unavailable — credits deduction timed out
            (`deduct_credits_timeout`), or this endpoint is restricted to admin
            API keys (`FEATURE_UNAVAILABLE`). Non-admin keys should use
            `undress-v3` via `POST /api/v1/pipelines` with `stages:
            [{"op":"undress-v3","biometric_consent":true,"real_person_consent":true}]`.'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/face-swap:
    post:
      operationId: faceSwap
      summary: Swap faces in an image
      tags:
        - generation
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                sourceImage:
                  type: string
                  minLength: 1
                targetImage:
                  type: string
                  minLength: 1
                biometricConsent:
                  type: boolean
                  enum:
                    - true
                faceRestoration:
                  type: boolean
                  default: true
              required:
                - sourceImage
                - targetImage
                - biometricConsent
      responses:
        "202":
          description: Job accepted and queued for processing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FaceSwapAcceptedResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          description: Insufficient credits.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Payload too large — a supplied image exceeds the size limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Validation error — a field failed schema validation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: GPU capacity unavailable — retry shortly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/enhance:
    post:
      operationId: enhance
      summary: Enhance / upscale an image
      tags:
        - generation
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  properties:
                    operation:
                      type: string
                      enum:
                        - upscale
                    source:
                      type: string
                    scale:
                      type: integer
                      minimum: 2
                      maximum: 4
                      default: 2
                  required:
                    - operation
                    - source
                - type: object
                  properties:
                    operation:
                      type: string
                      enum:
                        - face-restore
                    source:
                      type: string
                  required:
                    - operation
                    - source
                - type: object
                  properties:
                    operation:
                      type: string
                      enum:
                        - bg-replace
                    source:
                      type: string
                    background_prompt:
                      type: string
                      minLength: 1
                      maxLength: 2000
                  required:
                    - operation
                    - source
                    - background_prompt
                - type: object
                  properties:
                    operation:
                      type: string
                      enum:
                        - attach-object
                    source:
                      type: string
                    object_prompt:
                      type: string
                      minLength: 1
                      maxLength: 2000
                    mask:
                      type: string
                  required:
                    - operation
                    - source
                    - object_prompt
                    - mask
      responses:
        "202":
          description: Job accepted and queued for processing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnhanceAcceptedResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          description: Insufficient credits.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: The source job does not exist, or it has no stored output
            (`SOURCE_NOT_FOUND` in both cases).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Service temporarily unavailable — credits deduction timed out
            (`deduct_credits_timeout`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/pipelines:
    post:
      operationId: createPipeline
      summary: Submit a multi-stage image pipeline
      description: Add `?dry_run=true` to get a cost breakdown (200) without
        submitting the job (202).
      tags:
        - pipelines
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                stages:
                  type: array
                  items:
                    oneOf:
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - generate
                          prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                          negative_prompt:
                            type: string
                            maxLength: 2000
                          model:
                            type: string
                          loras:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                strength:
                                  type: number
                                  minimum: 0
                                  maximum: 2
                              required:
                                - id
                                - strength
                            maxItems: 2
                          width:
                            type: integer
                          height:
                            type: integer
                          seed:
                            type: integer
                        required:
                          - op
                          - prompt
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - undress
                          biometric_consent:
                            type: boolean
                            enum:
                              - true
                          real_person_consent:
                            type: boolean
                            enum:
                              - true
                        required:
                          - op
                          - biometric_consent
                          - real_person_consent
                        description: Admin API keys only; returns 503 for non-admin keys (BUG-1156).
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - undress-v2
                          biometric_consent:
                            type: boolean
                            enum:
                              - true
                          real_person_consent:
                            type: boolean
                            enum:
                              - true
                        required:
                          - op
                          - biometric_consent
                          - real_person_consent
                        description: Admin API keys only; returns 503 for non-admin keys (BUG-1156).
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - undress-v3
                          biometric_consent:
                            type: boolean
                            enum:
                              - true
                          real_person_consent:
                            type: boolean
                            enum:
                              - true
                        required:
                          - op
                          - biometric_consent
                          - real_person_consent
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - face-swap
                          biometric_consent:
                            type: boolean
                            enum:
                              - true
                          face_model_id:
                            type: string
                            format: uuid
                          face:
                            type: string
                        required:
                          - op
                          - biometric_consent
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - upscale
                          scale:
                            type: integer
                            minimum: 2
                            maximum: 4
                            default: 2
                        required:
                          - op
                        description: Admin API keys only; returns 403 for non-admin keys.
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - face-restore
                        required:
                          - op
                        description: Admin API keys only; returns 403 for non-admin keys.
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - fix-hand
                          strength:
                            type: integer
                            minimum: 0
                            maximum: 5
                        required:
                          - op
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - bg-replace
                          background_prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                        required:
                          - op
                          - background_prompt
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - attach-object
                          object_prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                          mask:
                            type: string
                        required:
                          - op
                          - object_prompt
                          - mask
                        description: Admin API keys only; returns 403 for non-admin keys.
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - attach-object-v2
                          object_prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                          mask:
                            type: string
                          prompt:
                            type: string
                            maxLength: 2000
                          seed:
                            type: integer
                        required:
                          - op
                          - object_prompt
                          - mask
                        description: Admin API keys only; returns 403 for non-admin keys.
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - attach-object-v3
                          object_prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                          mask:
                            type: string
                          reference:
                            type: string
                          prompt:
                            type: string
                            maxLength: 2000
                          seed:
                            type: integer
                        required:
                          - op
                          - object_prompt
                          - mask
                        description: Admin API keys only; returns 403 for non-admin keys.
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - animate
                          motion_prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                          frames:
                            type: integer
                        required:
                          - op
                          - motion_prompt
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - redress
                          biometric_consent:
                            type: boolean
                            enum:
                              - true
                          clothing_prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                        required:
                          - op
                          - biometric_consent
                          - clothing_prompt
                        description: Admin API keys only; returns 403 for non-admin keys.
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - redress-v2
                          biometric_consent:
                            type: boolean
                            enum:
                              - true
                          clothing_prompt:
                            type: string
                            minLength: 1
                            maxLength: 2000
                        required:
                          - op
                          - biometric_consent
                          - clothing_prompt
                      - type: object
                        properties:
                          op:
                            type: string
                            enum:
                              - redress-vton
                          biometric_consent:
                            type: boolean
                            enum:
                              - true
                          garment:
                            type: string
                            minLength: 1
                        required:
                          - op
                          - biometric_consent
                          - garment
                  minItems: 1
                  maxItems: 5
                source:
                  type: string
                webhook_event_id_prefix:
                  type: string
                  maxLength: 64
              required:
                - stages
      responses:
        "200":
          description: Dry-run cost breakdown (no job submitted).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PipelineDryRunResponse"
        "202":
          description: Pipeline accepted and queued for processing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PipelineCreatedResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          description: Insufficient credits.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`), the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`), a requested LoRA is
            not owned by the caller (`LORA_NOT_OWNED`), or the prompt was
            blocked (`PROMPT_BLOCKED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: A LoRA referenced by a stage does not exist (`LORA_NOT_FOUND`), or
            the supplied source job/output was not found (`SOURCE_NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Service temporarily unavailable — credits deduction timed out
            (`deduct_credits_timeout`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/pipelines/{id}:
    get:
      operationId: getPipeline
      summary: Get pipeline status
      tags:
        - pipelines
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            description: Pipeline ID (pipeline_<uuid>).
          required: true
          description: Pipeline ID (pipeline_<uuid>).
          name: id
          in: path
      responses:
        "200":
          description: Pipeline status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PipelineStatusResponse"
        "400":
          description: Bad request — the pipeline ID is not in the expected format
            (`pipeline_<uuid>`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Pipeline not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/jobs:
    get:
      operationId: listJobs
      summary: List generation jobs
      tags:
        - jobs
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
          required: false
          name: page
          in: query
        - schema:
            type: string
            enum:
              - pending
              - processing
              - completed
              - failed
          required: false
          name: status
          in: query
      responses:
        "200":
          description: Paginated list of jobs.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobListResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Unprocessable entity — the `status` query param is not one of the
            allowed values.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/jobs/{id}:
    get:
      operationId: getJob
      summary: Get job status / result
      tags:
        - jobs
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            description: Job ID.
          required: true
          description: Job ID.
          name: id
          in: path
      responses:
        "200":
          description: Job details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobDetailResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Job not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: cancelJob
      summary: Cancel a pending job
      tags:
        - jobs
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            description: Job ID.
          required: true
          description: Job ID.
          name: id
          in: path
      responses:
        "200":
          description: Job cancelled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobCancelResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Job not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Job cannot be cancelled (not in a cancellable state).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/webhooks:
    get:
      operationId: listWebhooks
      summary: List webhooks
      tags:
        - webhooks
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Webhook list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookListResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: createWebhook
      summary: Create a webhook
      description: The `secret` field is only returned at creation time.
      tags:
        - webhooks
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                    enum: *a1
                  minItems: 1
                  maxItems: 8
              required:
                - url
                - events
      responses:
        "201":
          description: Webhook created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookCreateResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict — the per-account webhook limit has been reached
            (`WEBHOOK_LIMIT_REACHED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/webhooks/{id}:
    get:
      operationId: getWebhook
      summary: Get webhook details
      tags:
        - webhooks
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Webhook ID.
          required: true
          description: Webhook ID.
          name: id
          in: path
      responses:
        "200":
          description: Webhook details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookGetResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      operationId: updateWebhook
      summary: Update a webhook
      tags:
        - webhooks
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Webhook ID.
          required: true
          description: Webhook ID.
          name: id
          in: path
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                    enum: *a1
                  minItems: 1
                  maxItems: 8
                is_active:
                  type: boolean
      responses:
        "200":
          description: Webhook updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookUpdateResponse"
        "400":
          description: Bad request — malformed JSON body, validation failure, no updatable
            fields provided, or `url` is not a valid HTTPS URL.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteWebhook
      summary: Delete a webhook
      tags:
        - webhooks
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Webhook ID.
          required: true
          description: Webhook ID.
          name: id
          in: path
      responses:
        "200":
          description: Webhook deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeleteResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/webhooks/{id}/deliveries:
    get:
      operationId: listWebhookDeliveries
      summary: List webhook delivery attempts
      tags:
        - webhooks
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Webhook ID.
          required: true
          description: Webhook ID.
          name: id
          in: path
      responses:
        "200":
          description: Delivery history.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryListResponse"
        "400":
          description: Bad request — the `status` filter is not a valid delivery status
            value.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/webhooks/{id}/test:
    post:
      operationId: testWebhook
      summary: Send a test delivery
      tags:
        - webhooks
      security:
        - BearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Webhook ID.
          required: true
          description: Webhook ID.
          name: id
          in: path
      responses:
        "202":
          description: Test delivery queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookTestResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — the account is suspended (`FORBIDDEN`) or the API key
            lacks the required scope (`INSUFFICIENT_SCOPE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict — the webhook is inactive and cannot receive test
            deliveries (`WEBHOOK_INACTIVE`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "451":
          description: Unavailable for legal reasons — geo-blocked region or age-assurance
            block enforced by middleware. Body is plain text (`Unavailable in
            your region`), not a JSON envelope. Production-only.
          content:
            text/plain:
              schema:
                type: string
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
webhooks: {}
