Gemini 3.1 Pro Preview: public image URLs return 403 (native) / 400 (OpenAI-compatible), while inline images work

Hi Google team,

We are investigating external image URL input with gemini-3.1-pro-preview on the Gemini Developer API, using an API key from AI Studio.

On the same API key and model, public HTTPS image URLs fail when passed as image parts, while downloading the same images and sending their bytes succeeds. Could you help clarify the supported behavior and investigate the failures?

The file input documentation describes external HTTP and signed URLs as a supported input method. We understand that the separate image understanding tutorial downloads a URL on the client before calling Part.from_bytes; that byte-input path works for us.

Environment and scope

  • Model: gemini-3.1-pro-preview.
  • API: generativelanguage.googleapis.com, v1beta.
  • Rechecked on September 15, 2026, approximately 04:52–04:57 UTC.
  • Python SDKs: google-genai==2.23.0, openai==3.14.0; HTTP client: httpx==0.28.1.
  • Small functional probes, one credential, maximum two simultaneous requests; no load testing or automatic retries.
  • Minimal requests omitted optional token limits, reasoning settings, and service-tier options. We also inspected the SDKs’ outgoing JSON.

Observed results

Input method Result
Native REST fileData.fileUri with the correct MIME type 403 PERMISSION_DENIED
Native REST using file_data.file_uri spelling Same 403
Google SDK Part.from_uri Same 403
OpenAI-compatible REST image_url: {"url": "https://..."} 400 INVALID_ARGUMENT
Official OpenAI SDK with the same HTTPS image URL Same 400
OpenAI-compatible HTTPS input with detail: "auto" Same 400
The same image bytes via Google SDK Part.from_bytes 200, correct image description
The same image bytes via OpenAI SDK base64 data URI 200, correct image description
A URI returned by a Gemini Files API upload, tested separately the same day 200, successful generation

The native error body was:

{"error":{"code":403,"message":"The caller does not have permission","status":"PERMISSION_DENIED"}}

The OpenAI-compatible error body was:

{"error":{"code":400,"message":"Request contains an invalid argument.","status":"INVALID_ARGUMENT"}}

Our public CloudFront PNG returned HTTP 200 without authentication or redirects, with the correct image/png header and a valid PNG signature. It was only 52,440 bytes. We also reproduced the REST failures with the Google-hosted JPEG reached from https://goo.gle/instrument-img, used in the official image tutorial. Both the short URL and its resolved URL failed in native requests; the resolved URL also failed through the OpenAI-compatible endpoint. Downloading that JPEG and passing it with Part.from_bytes succeeded.

We realize that successful access from our client does not guarantee access from Google’s fetch infrastructure. However, these tests distinguish the issue from an inaccessible URL on our client or invalid image bytes.

Minimal REST reproduction using the official tutorial image

The following Python example constructs the native and OpenAI-compatible request shapes tested above. Set GEMINI_API_KEY in your environment. Running it makes two model requests.

import os
import httpx

api_key = os.environ["GEMINI_API_KEY"]
base = "https://generativelanguage.googleapis.com/v1beta"
model = "gemini-3.1-pro-preview"
prompt = (
    "Describe the image in at most 12 English words. "
    "If no image is attached, reply NO_IMAGE."
)

with httpx.Client(timeout=90.0, follow_redirects=True) as client:
    image = client.get("https://goo.gle/instrument-img")
    image.raise_for_status()
    image_url = str(image.url)
    print("Source:", image.status_code, image.headers.get("content-type"))

    native = client.post(
        f"{base}/models/{model}:generateContent",
        headers={"x-goog-api-key": api_key},
        json={"contents": [{"parts": [
            {"text": prompt},
            {"fileData": {"fileUri": image_url, "mimeType": "image/jpeg"}},
        ]}]},
    )
    print("Native:", native.status_code, native.text)

    compatible = client.post(
        f"{base}/openai/chat/completions",
        headers={"Authorization": f"Bearer {api_key}"},
        json={
            "model": model,
            "messages": [{"role": "user", "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {"url": image_url}},
            ]}],
        },
    )
    print("OpenAI-compatible:", compatible.status_code, compatible.text)

For malformed-schema controls, sending contents to the OpenAI-compatible endpoint or messages to the native endpoint returned explicit unknown-field errors. Those errors differed from the generic failures above. We are not assuming that this identifies the exact internal failure stage.

URL context is a partial alternative in our tests

Putting a URL in the prompt and enabling url_context, without attaching a fileData image part, successfully retrieved our CloudFront image twice. The responses included URL_RETRIEVAL_STATUS_SUCCESS, image tokens in tool input usage, and descriptions matching the actual image. A public Google GCS sample image also worked.

However, the official tutorial’s resolved gstatic JPEG returned HTTP 200 with URL_RETRIEVAL_STATUS_ERROR and a retrieval-failure answer through URL context. Adding url_context while retaining the failing fileData part still returned 403. We therefore cannot treat URL context as an equivalent, consistently working replacement for direct image input.

Questions

  1. Are external public HTTPS image URLs currently supported by gemini-3.1-pro-preview through native fileData.fileUri? Does support also extend to HTTPS URLs in the OpenAI-compatible image_url.url field, or only to data URIs there?
  2. Are there project eligibility, rollout, billing, host-access, redirect, or other requirements beyond those described in the file input documentation?
  3. Do the native 403 and OpenAI-compatible 400 indicate a known external-media retrieval issue? Can the error response expose a more specific reason to distinguish project authorization from remote fetch or URL-validation failures?
  4. What supported approach should we use when images already reside at public CDN URLs and we want to avoid downloading and base64-encoding each image in our application servers? Is URL context intended for this use case, with the same image fidelity and resolution controls as direct image input, or should we use the Files API until direct URL behavior is clarified?

Our use case is interactive image-based analysis with bursts of concurrent requests, so client-side download/re-encoding and extra tool retrieval steps affect memory use and latency.

The September 7 and September 10 replies in this related thread also report failures with external S3 URLs while byte input works. We cannot confirm a shared root cause; unlike that thread’s original Files API issue, our uploaded Files API URI succeeded.

Thanks for clarifying the current support contract and the recommended implementation.

This is a known issue from at least over a month ago.

It use to return 429 errors, and now since Sept 6th or 7th it has returned denied errors.

The only work around has been to upload or register the files sadly so far.

It may work under vertex but I haven’t checked that in a while either.