yikart/AiToEarn · error · BadRequestException

image aspectRatio must use positive integer WIDTH:HEIGHT

Error message

image aspectRatio must use positive integer WIDTH:HEIGHT

What it means

After splitting aspectRatio into two parts, resolveOpenAIImageSize coerces both to Number and requires them to be positive integers. Non-numeric tokens, floats, zero, or negative values throw this BadRequestException. It refines the format error [90] with numeric constraints.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-ai/src/core/draft-generation/draft-generation.service.ts:280

    const modelConfig = config.ai.draftGeneration.imageModels.find(m => m.model === model)
    if (!modelConfig) {
      throw new AppException(ResponseCode.InvalidModel)
    }

    return modelConfig
  }

  private resolveOpenAIImageSize(aspectRatio?: string, imageSize = OPENAI_IMAGE_DEFAULT_RESOLUTION): string {
    const sizeProfile = getOpenAIImageSizeProfile(imageSize)
    const parts = (aspectRatio ?? OPENAI_IMAGE_DEFAULT_ASPECT_RATIO).split(':')
    if (parts.length !== 2) {
      throw new BadRequestException('image aspectRatio must use WIDTH:HEIGHT')
    }

    const widthRatio = Number(parts[0])
    const heightRatio = Number(parts[1])
    if (!Number.isInteger(widthRatio) || !Number.isInteger(heightRatio) || widthRatio <= 0 || heightRatio <= 0) {
      throw new BadRequestException('image aspectRatio must use positive integer WIDTH:HEIGHT')
    }

    const ratio = widthRatio / heightRatio
    if (ratio < OPENAI_IMAGE_MIN_ASPECT_RATIO || ratio > OPENAI_IMAGE_MAX_ASPECT_RATIO) {
      throw new BadRequestException('image aspectRatio must be between 1:3 and 3:1')
    }

    if (widthRatio === heightRatio) {
      return sizeProfile.squareSize ?? this.resolveScaledOpenAIImageSize(widthRatio, heightRatio, sizeProfile)
    }

    return this.resolveScaledOpenAIImageSize(widthRatio, heightRatio, sizeProfile)
  }

  private resolveScaledOpenAIImageSize(
    widthRatio: number,
    heightRatio: number,
    sizeProfile: { maxEdge: number, maxPixels: number },

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Use positive integer ratio components, e.g. '3:2' instead of '1.5:1'.
  2. Round/multiply fractional ratios to integers client-side before sending.
  3. Trim inputs and validate with a regex like /^\d+:\d+$/ before the API call.
  4. Keep ratios within 1:3..3:1 to also pass the range check (error 92).

Example fix

// before
aspectRatio: `${width/height}:1` // '1.5:1'
// after
const g = gcd(width, height)
aspectRatio: `${width/g}:${height/g}` // '3:2'
Defensive patterns

Strategy: validation

Validate before calling

function isValidRatio(a, b) { return Number.isInteger(Number(a)) && Number.isInteger(Number(b)) && Number(a) > 0 && Number(b) > 0 }
const [w, h] = String(aspectRatio).split(':')
if (!isValidRatio(w, h)) throw new Error('aspectRatio must use positive integers, e.g. 3:2 not 1.5:1')

Type guard

function isIntegerRatio(v: unknown): v is string {
  if (typeof v !== 'string') return false
  const [w, h] = v.split(':').map(Number)
  return Number.isInteger(w) && Number.isInteger(h) && w > 0 && h > 0
}

Try / catch

try {
  await generateDraft({ aspectRatio })
} catch (e) {
  if (e.status === 400 && e.message.includes('positive integer')) {
    aspectRatio = toIntegerRatio(aspectRatio) // e.g. scale 1.5:1 -> 3:2 and retry
  } else throw e
}

Prevention

When it happens

Trigger: aspectRatio like '16.5:9', 'a:b', '0:9', '-16:9', or 'NaN:1' passed to draft generation's OpenAI image size resolution.

Common situations: Passing fractional ratios like '1.5:1' instead of scaling to integers ('3:2'); empty string halves producing NaN; programmatic ratio computation producing floats.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/822fdfdc510b477d. Report an issue: GitHub.