Skip to content

Responses API rejects base64 input_image data URIs longer than ~65,520 chars with invalid_payload / "is not a valid absolute URI" #3551

Description

@savvasp-123

Confirm this is an issue with the Python library and not an underlying OpenAI API

  • This is an issue with the Python library

Describe the bug

Summary

Calling the Responses API (client.responses.parse) with an input_image whose image_url is a base64 data URI fails with HTTP 400 when the data URI string exceeds roughly 65,520 characters:

Error code: 400 - {'error': {'code': 'invalid_payload', 'message': "'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcHBgUICAcHCQkICgwUDQwLC...' is not a valid absolute URI"}}

The same request with a smaller image succeeds. The limit applies per image_url string, not to the total request size.

The cutoff sits almost exactly on System.Uri's maximum length in .NET (0xFFF0 = 65,520), which suggests the service validates the data URI with a URI parser that enforces a length cap. Data URIs have no such length limit, and the error message describes the value as malformed rather than too long, which made this take a long time to diagnose.

Measured behaviour

Each row is one request. Images are JPEG, 1024x1448 RGB, varying only in encoded size. "URI chars" is len("data:image/jpeg;base64,") + len(b64).

Request URI chars (per image) Result
Text only, no images n/a 200 OK
5 images 62,087 each (310,268 total) 200 OK
1 image 62,087 200 OK
1 image 63,975 200 OK
1 image 65,883 400 invalid_payload
1 image 78,427 400 invalid_payload
1 image 128,623 400 invalid_payload
1 image 196,399 400 invalid_payload
1 image 330,187 400 invalid_payload

Accept/reject boundary is between 63,975 and 65,883 characters, bracketing 65,520.

The limit is per-image, not per-request. Five images totalling 310,268 characters are accepted, while a single 65,883-character image is rejected. Splitting a batch into more requests therefore does not help; only shrinking each individual image does.

Reproduction

import base64
from io import BytesIO

from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import DefaultAzureCredential
from PIL import Image
from pydantic import BaseModel


class Result(BaseModel):
    label: str


def make_image_b64(noise_alpha: float) -> str:
    """1024x1448 JPEG. Higher noise_alpha -> larger encoded size."""
    noise = Image.effect_noise((1024, 1448), 64.0)
    speckle = Image.merge("RGB", (noise, noise, noise))
    base = Image.new("RGB", (1024, 1448), color="white")
    image = Image.blend(base, speckle, noise_alpha)
    with BytesIO() as buffered:
        image.save(buffered, format="JPEG")
        return base64.b64encode(buffered.getvalue()).decode("utf-8")


async def main():
    async with DefaultAzureCredential() as credential:
        async with AIProjectClient(credential=credential, endpoint="<project-endpoint>") as project:
            async with project.get_openai_client() as client:
                for alpha in (0.0, 0.06, 1.0):  # ~62k, ~66k, ~1M URI chars
                    b64 = make_image_b64(alpha)
                    uri = f"data:image/jpeg;base64,{b64}"
                    print(f"uri chars = {len(uri)}")
                    try:
                        await client.responses.parse(
                            model="<deployment-name>",
                            instructions="Describe the image.",
                            input=[
                                {
                                    "role": "user",
                                    "content": [
                                        {"type": "input_text", "text": "Describe this:"},
                                        {"type": "input_image", "image_url": uri},
                                    ],
                                }
                            ],
                            text_format=Result,
                            reasoning={"effort": "low"},
                        )
                        print("  -> accepted")
                    except Exception as e:
                        print(f"  -> {e}")

Output:

uri chars = 62087
  -> accepted
uri chars = 65883
  -> Error code: 400 - {'error': {'code': 'invalid_payload', 'message': "'data:image/jpeg;base64,...' is not a valid absolute URI"}}
uri chars = 1035135
  -> Error code: 400 - {'error': {'code': 'invalid_payload', 'message': "'data:image/jpeg;base64,...' is not a valid absolute URI"}}

Expected behaviour

A valid base64 data URI should be accepted regardless of length, up to the documented request/image size limits for the model. If a length cap is intentional, it should be documented and the error should state that the image exceeds the maximum size rather than reporting the value as not a valid URI.

Impact

This makes base64 image input unusable at any realistic document resolution. A single page of a scanned PDF rendered at 300 DPI / 1024x1448 encodes to roughly 440,000 base64 characters, about 6.7x over the limit. To fit under 65,520 characters the same page has to be rendered at roughly 340x481, which is too low a resolution for document classification and OCR-adjacent tasks.

Because the error names the value as malformed rather than oversized, and because the same code path works for small images, this presents as an intermittent "some documents fail" bug in production rather than a size limit.

Environment

  • openai 2.34.0
  • azure-ai-projects 2.1.0
  • azure-identity 1.25.3
  • Python 3.13.11, Linux
  • Client obtained via AIProjectClient.get_openai_client()
  • Endpoint: Azure AI Foundry project endpoint
  • API: Responses API, client.responses.parse with text_format (structured outputs) and reasoning={"effort": "low"}

Related

  • #46305 — also invalid_payload on base64 image input to the Responses API, but with a different message (The provided data does not match the expected schema) and no length component. Closed as addressed. This report is a distinct failure mode: valid, well-formed data URIs rejected purely on length.

Notes

  • Reproduced consistently across many requests; not intermittent.
  • Only the image size varies between the passing and failing requests. Message structure, deployment, schema, reasoning, and client construction are identical.
  • Not an SDK-side validation failure: the SDK serialises the string unchanged and the rejection comes back from the service as an HTTP 400.

To Reproduce

Reproduction

import base64
from io import BytesIO

from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import DefaultAzureCredential
from PIL import Image
from pydantic import BaseModel


class Result(BaseModel):
    label: str


def make_image_b64(noise_alpha: float) -> str:
    """1024x1448 JPEG. Higher noise_alpha -> larger encoded size."""
    noise = Image.effect_noise((1024, 1448), 64.0)
    speckle = Image.merge("RGB", (noise, noise, noise))
    base = Image.new("RGB", (1024, 1448), color="white")
    image = Image.blend(base, speckle, noise_alpha)
    with BytesIO() as buffered:
        image.save(buffered, format="JPEG")
        return base64.b64encode(buffered.getvalue()).decode("utf-8")


async def main():
    async with DefaultAzureCredential() as credential:
        async with AIProjectClient(credential=credential, endpoint="<project-endpoint>") as project:
            async with project.get_openai_client() as client:
                for alpha in (0.0, 0.06, 1.0):  # ~62k, ~66k, ~1M URI chars
                    b64 = make_image_b64(alpha)
                    uri = f"data:image/jpeg;base64,{b64}"
                    print(f"uri chars = {len(uri)}")
                    try:
                        await client.responses.parse(
                            model="<deployment-name>",
                            instructions="Describe the image.",
                            input=[
                                {
                                    "role": "user",
                                    "content": [
                                        {"type": "input_text", "text": "Describe this:"},
                                        {"type": "input_image", "image_url": uri},
                                    ],
                                }
                            ],
                            text_format=Result,
                            reasoning={"effort": "low"},
                        )
                        print("  -> accepted")
                    except Exception as e:
                        print(f"  -> {e}")

Code snippets

OS

Linux

Python version

Python 3.13.11

Library version

2.34.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions