# 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: 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 `): ``` Content-Length: 200 X-Goog-Upload-Offset: 0 X-Goog-Upload-Command: upload, finalize ``` → `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 ```json {"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/"`, ≤ 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 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": "", "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|GENERATED|FAILED, error}`; `GET …/generatedFiles/{id}/operations/{op}` for LROs | ## 5. Using a file ```json {"contents": [{"role": "user", "parts": [{"text": "What is the secret word?"}, {"fileData": {"fileUri": "", "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).