Gemini Files API — upload protocol, files.*, files:register, generatedFiles
Status: DOCUMENTED + LIVE_VERIFIED (resumable, multipart and raw uploads; get/list/delete; register of a public GCS object; generatedFiles list; 2026-09-18).
Sources: https://ai.google.dev/api/files · https://ai.google.dev/gemini-api/docs/generate-content/files · https://ai.google.dev/gemini-api/docs/generate-content/file-input-methods · discovery media.upload (mediaUpload.protocols), File, RegisterFilesRequest
Machine-readable: generated/fragments/parameters/gemini-files.json, generated/fragments/endpoints/gemini-core.json (files/media/generatedFiles), generated/fragments/objects/gemini-core-objects.json#File
Last verified: 2026-09-18
1. Limits and lifetime
| Value | |
|---|---|
| Per file | 2 GB (mediaUpload.maxSize 2147483648) |
| Per project | 20 GB |
| Retention | 48 h (expirationTime = create + 48 h observed), then auto-deleted |
| Cost | free, all regions |
| Download | user uploads cannot be downloaded (metadata only); generated files via downloadUri |
| When to use | total request > 20 MB (image/audio guides) / > 100 MB (file-input-methods; PDFs > 50 MB), or reuse across requests |
2. Resumable upload — step by step (verified)
Step 1 — start session (POST https://generativelanguage.googleapis.com/upload/v1beta/files, JSON metadata):
x-goog-api-key: <key>
X-Goog-Upload-Protocol: resumable
X-Goog-Upload-Command: start
X-Goog-Upload-Header-Content-Length: 200
X-Goog-Upload-Header-Content-Type: text/plain
Content-Type: application/json
{"file": {"display_name": "atlas-probe.txt"}}→ 200, empty body, response headers: X-Goog-Upload-URL: https://generativelanguage.googleapis.com/upload/v1beta/files?upload_id=…&upload_protocol=resumable, X-Goog-Upload-Control-URL, X-Goog-Upload-Status: active, X-Goog-Upload-Chunk-Granularity: 8388608 (8 MiB).
Step 2 — send bytes (POST <X-Goog-Upload-URL>):
Content-Length: 200
X-Goog-Upload-Offset: 0
X-Goog-Upload-Command: upload, finalize
<raw bytes>→ 200 {"file": {...}} + X-Goog-Upload-Status: final. For chunked uploads send X-Goog-Upload-Command: upload per chunk (offsets multiple of the granularity) and finalize last; query/cancel go to the control URL. The upload URL contains the session id, not your API key — but still keep it out of logs.
Alternatives (verified): POST /upload/v1beta/files?uploadType=multipart with Content-Type: multipart/related; boundary=… (part 1 application/json metadata, part 2 the media) → 200; POST /upload/v1beta/files?uploadType=media with raw bytes and the media Content-Type → 200 (no display name).
3. File object
{"name": "files/w7x6n2hlpxrq", "displayName": "atlas-probe.txt", "mimeType": "text/plain", "sizeBytes": "174",
"createTime": "…", "updateTime": "…", "expirationTime": "2026-09-21T03:46:49.478571266Z",
"sha256Hash": "ZmVhNTIx…", "uri": "https://generativelanguage.googleapis.com/v1beta/files/w7x6n2hlpxrq",
"state": "ACTIVE", "source": "UPLOADED"}state ∈ PROCESSING | ACTIVE | FAILED (+ error: Status) — poll files.get until ACTIVE for video/audio (tiny txt/PDF were ACTIVE immediately). source ∈ UPLOADED | GENERATED | REGISTERED. videoMetadata.videoDuration for videos. Custom ids: file.name = "files/<id>", ≤ 40 chars [a-z0-9-], no leading/trailing dash. sha256Hash is base64 of the hex digest string.
4. Other methods
| Method | Result (live) |
|---|---|
GET /v1beta/files?pageSize=&pageToken= |
{"files": [...], "nextPageToken"?}; also /v1/files |
GET /v1beta/files/{id} |
File; unknown or deleted → 403 PERMISSION_DENIED You do not have permission to access the File <id> or it may not exist. (never 404) |
DELETE /v1beta/files/{id} |
{}; later fileData use → 403 |
POST /v1beta/files:register {"uris": ["gs://bucket/object"]} |
registers GCS objects without copying; live with the public gs://cloud-samples-data/generative-ai/image/scones.jpg → {"files": [{"name": "files/…", "displayName": "<id>", "mimeType": "image/jpeg", "sizeBytes": "394671", "uri": "…", "source": "REGISTERED"}]} (no state/expirationTime; docs: valid up to 30 days, 2 GB/file, no storage cap). One failure fails the whole request. |
GET /v1beta/generatedFiles?pageSize= |
{} (none); items `{name: generatedFiles/…, mimeType, state: GENERATING |
5. Using a file
{"contents": [{"role": "user", "parts": [{"text": "What is the secret word?"}, {"fileData": {"fileUri": "<File.uri>", "mimeType": "text/plain"}}]}]}mimeType optional (taken from the File). Verified: txt → PELICAN; 1-page PDF → ATLAS (536 prompt tokens: IMAGE 520 + TEXT 16; countTokens reports the same page as DOCUMENT 560).
6. SDK
Python client.files.upload(file=path|io, config=types.UploadFileConfig(mime_type=, display_name=, name=)), client.files.get(name=), .list(), .delete(name=); Node ai.files.upload({file, config:{mimeType, displayName}}), ai.files.get/list/delete. SDKs implement the resumable protocol. Examples: examples/gemini/files/ (curl script does the two-step protocol by hand).