Why does a Gemini call fail immediately after uploading a video via the Files API?
answer
- upload returns before the file is ready
- there is a state field, read it
- three states, not two
- poll on the resource name
- the other end is a 48-hour clock
basics
~20 sLarge uploads come back in PROCESSING state, and referencing the file before it turns ACTIVE is rejected with a 400 FAILED_PRECONDITION. The fix is to poll files.get on the file name until state is ACTIVE, and to treat FAILED as a re-upload.
solid answer
~50 s`client.files.upload(...)` returns as soon as the bytes are received, but a video is not usable yet: the returned File carries `state = PROCESSING`. Referencing that URI in `generateContent` fails with a 400 **FAILED_PRECONDITION** saying the file is not in an ACTIVE state. The correct pattern is a poll loop — call `client.files.get(name=f.name)` on an interval until `state` is `ACTIVE`, bail out if it becomes `FAILED`, and only then issue the generation call. Because a naive integration works fine on a 2 MB clip and breaks on a 200 MB one, this bug typically escapes local testing and surfaces in production. The other lifecycle failure is at the opposite end: files are deleted after 48 hours, so a stored URI eventually stops resolving, and files are scoped to the uploading project, so a URI is not portable between API keys.
code
python · 19 linesimport time
from google import genai
client = genai.Client(api_key="YOUR_KEY")
video = client.files.upload(file="lecture.mp4")
deadline = time.time() + 600
while video.state.name == "PROCESSING" and time.time() < deadline:
time.sleep(5)
video = client.files.get(name=video.name)
if video.state.name != "ACTIVE":
raise RuntimeError(f"file not usable: {video.state.name}")
reply = client.models.generate_content(
model="gemini-2.5-flash",
contents=[video, "Summarise the key points."],
)
print(reply.text)go deeper
Remember that uploading a video and using it are two steps: check that the file's state says ACTIVE before you reference it in a request.
Explain the state machine and the exact symptom — a 400 FAILED_PRECONDITION when a PROCESSING file is referenced — and write the files.get poll loop that fixes it.
Show the production shape: a bounded poll with backoff, an explicit FAILED branch, and re-upload-and-retry handling for the 48-hour expiry that a late retry will hit.
Own the ingestion contract across services — where authoritative bytes live, which service holds the Gemini project scope, and how storage quota and cleanup are managed at volume.
## Upload is not availability The Files API separates *receiving* bytes from *preparing* them. For images and small documents preparation is effectively instantaneous, so the File comes back already `ACTIVE`. For video — and for large media generally — Google has server-side work to do first, and the upload call returns a File whose `state` is `PROCESSING`. A Part referencing a `PROCESSING` file is not queued or waited on. The generation request is rejected immediately with HTTP **400** and status **FAILED_PRECONDITION**, with a message to the effect that the file is not in an ACTIVE state. ## The correct pattern Upload, then loop: sleep a few seconds, re-fetch the file with `client.files.get(name=f.name)`, and continue while the state is `PROCESSING`. Only once it reports `ACTIVE` do you build the Part and call `generate_content`. Three details matter. First, poll with `files.get(name=f.name)` — the resource name (`files/abc123`), not the URI. Second, handle the third state: `FAILED` means the media could not be prepared at all (corrupt container, unsupported codec), and no amount of further polling will change it; the correct response is to surface the error or re-encode and re-upload, not to loop forever. Third, bound the loop — a wall-clock deadline plus a backoff, so a stuck file cannot pin a worker indefinitely. ## Why this bug hides Developers test with a small sample clip that becomes ACTIVE before the next line of code runs, so the missing poll loop is invisible. In production, real user uploads are minutes long, processing takes real time, and the failure appears as intermittent 400s that correlate suspiciously with file size. Any code path that uploads video without a state check should be treated as broken regardless of whether it has failed yet. ## The other end of the lifecycle: expiry Files are retained for **48 hours** and then deleted; the File resource carries an `expiration_time` you can read. A `file_uri` is therefore a short-lived handle. Systems that persist the URI as though it identified the document — in a database row, a job payload, a retry queue that might drain late — break silently two days later when the reference no longer resolves. The robust shape: your own object storage holds the authoritative bytes, and the Gemini file is a cache entry keyed by your document id. On a missing or expired file error, re-upload and retry once; only then surface a failure. That also handles the case where a file was deleted early, deliberately, by `client.files.delete(name=...)`. ## Scope and visibility Files belong to the project and API key that uploaded them. A URI minted by your staging key will not resolve for your production key, and a URI cannot be shared with another party as a download link — the API deliberately exposes metadata only, with no way to read the bytes back out. If several services need the same media, each needs its own upload, or one service owns Gemini interaction on behalf of the others. ## Quotas as a lifecycle concern Storage is free but bounded — on the order of 20 GB per project, with a 2 GB ceiling per file. A high-volume pipeline that uploads and never deletes will eventually hit the project cap even though everything expires eventually, because expiry lags ingestion. Explicitly deleting files once a job completes keeps headroom and is one line: `client.files.delete(name=f.name)`. ## What a strong answer sounds like Name the state machine (PROCESSING to ACTIVE, or FAILED), name the error you get for jumping the gun (400 FAILED_PRECONDITION), describe a bounded poll on `files.get`, and then connect it to the two adjacent lifecycle facts — 48-hour expiry making URIs non-durable, and project scoping making them non-portable. That combination shows someone who has actually run video through this API rather than read the quickstart.
- What does a FAILED file state mean, and how should the code react?FAILED means Google could not prepare the media at all — a corrupt container or an unsupported codec, typically. Polling will never turn it ACTIVE, so the loop must exit on it. React by surfacing a clear user-facing error or by re-encoding to a supported format and uploading again; retrying the identical bytes just reproduces the failure.
- A retry queue replays a job three days later using a stored file_uri. What happens?The reference no longer resolves, because Files API objects are deleted after 48 hours. Store your own document identifier in the job payload rather than the Gemini URI, and have the worker upload (or re-upload) as its first step. Treat a missing-file error as a normal branch that re-uploads and retries once before failing the job.
- Can you hand a Gemini file_uri to another service or team to use?No. Files are scoped to the project and API key that uploaded them, so another key's request will not resolve the URI, and the API exposes metadata only — there is no way to download the bytes back. Either give the other service access to your own object storage, or centralise Gemini calls behind one service that owns the uploads.
saying these in an interview costs you the question
- Assumes a file is usable the moment upload returns
- Polls forever without handling the FAILED state
- Stores file URIs as permanent document identifiers
- Thinks a URI works across different API keys
- Reads the 400 as a model error rather than a file-state error