yikart/AiToEarn · error · BadRequestException

image aspectRatio must be between 1:3 and 3:1

Error message

image aspectRatio must be between 1:3 and 3:1

What it means

Once the ratio parses to positive integers, resolveOpenAIImageSize enforces OPENAI_IMAGE_MIN_ASPECT_RATIO..OPENAI_IMAGE_MAX_ASPECT_RATIO (1/3..3). Ratios outside that band cannot map to a supported OpenAI image size and throw this BadRequestException.

Source

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

    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 },
  ): string {
    const divisor = getGreatestCommonDivisor(widthRatio, heightRatio)
    const reducedWidth = widthRatio / divisor
    const reducedHeight = heightRatio / divisor
    const maxScaleByEdge = Math.floor(sizeProfile.maxEdge / Math.max(reducedWidth, reducedHeight))

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Clamp the ratio to the supported 1:3..3:1 band before sending.
  2. Pick the closest supported ratio, e.g. '1:3' instead of '1:4'.
  3. Crop or letterbox the target creative concept to a supported ratio.
  4. Compute width/height locally and cap the ratio at min(w/h, 3) or floor(max(w/h, 1/3)).

Example fix

// before
aspectRatio: '1:4' // ratio 0.25 < 1/3
// after
aspectRatio: '1:3' // ratio 0.333, supported
Defensive patterns

Strategy: validation

Validate before calling

function ratioInRange(w, h) { const r = w / h; return r >= 1/3 && r <= 3 }
const [w, h] = aspectRatio.split(':').map(Number)
if (!ratioInRange(w, h)) aspectRatio = clampRatio(w, h) // clamp to 1:3..3:1

Type guard

function isSupportedRatio(v: string): boolean {
  const [w, h] = v.split(':').map(Number)
  const r = w / h
  return r >= 1 / 3 && r <= 3
}

Try / catch

try {
  await generateDraft({ aspectRatio })
} catch (e) {
  if (e.status === 400 && e.message.includes('between 1:3 and 3:1')) {
    aspectRatio = clampToSupportedRatio(aspectRatio) // e.g. 1:4 -> 1:3
  } else throw e
}

Prevention

When it happens

Trigger: aspectRatio like '1:4' (ratio 0.25) or '4:1' (ratio 4) — extreme portrait/landscape ratios beyond 1:3 and 3:1.

Common situations: Generating tall banners (9:32) or wide headers (32:9) for social platforms; passing raw platform creative dimensions without clamping.

Related errors


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