Skip to main content

Read this first

Google Drive delivery works, but two current limitations decide how you must configure it.
1. The dashboard cannot save Drive as an organisation default today.The dashboard writes org-level storage through POST /settings/org-storage, which is gated by the ORG_STORAGE_PROVIDERS setting. That setting currently defaults to blob (Azure Blob only), so a Drive request is rejected before any credential is read:
This is deliberate, not a misconfiguration. The Tornado API’s POST /user/gdrive handler does not accept the apply_to_all_keys flag that the org path depends on, so adding gdrive to ORG_STORAGE_PROVIDERS would not produce an org default. It would silently write a per-key config on whichever key the dashboard picked to authenticate its internal call, then fail the response check with The Tornado API did not confirm org-level scope.Until that ships, configure Drive with the direct API, POST /user/gdrive, on each API key you send jobs with. See Configure Tornado.
2. Do not configure Drive during onboarding. Do it from Settings afterwards.The onboarding wizard runs its steps in this order: destinationcredentialspaymentkeyrequestdelivered. The storage step therefore runs two steps before the API key is created.The Drive tile posts to POST /settings/org-storage, and that route resolves an existing active key from your organisation to authenticate its internal call. A brand new organisation has none, so the wizard returns:
Finish onboarding (pick Tornado bucket at the destination step, or any provider you already have credentials for), then configure Drive against your real API key.

What Drive delivery is, and is not

Tornado authenticates to Drive as a Google service account using a signed JWT, the same credential type as Google Cloud Storage. There is no OAuth consent screen and no “sign in with Google” redirect. You create the service account, you share a folder with it, you paste its JSON key. The backend requests exactly one scope:
Google lists drive.file under non-sensitive scopes and marks it Recommended. It grants the ability to “Create new Drive files, or modify existing files, that you open with an app or that the user shares with an app while using the Google Picker API or the app’s file picker”. In practice, for a server-side service account with no file picker, that means Tornado sees only the files Tornado itself created.
If you point Tornado at a folder that already contains 500 files, Tornado cannot list, read, overwrite or delete any of them. It can only add new files alongside them, and manage those. This is a deliberate least-privilege choice, and it is also why Tornado can never be used to exfiltrate the rest of your Drive.
Other behaviours worth knowing before you commit:
base_folder and folder_prefix do not build a folder tree in Drive. A job with base_folder: "premier" and folder_prefix: "alex/test/" produces one file in your destination folder literally named premier/alex/test/my-video.mp4. The generic Folder Prefix diagrams in the Custom Storage page describe the object-storage providers, not Drive.

The step everyone misses: share the folder with the service account

A service account is a Google identity with its own email address. It is not you, it cannot see your Drive, and it is not a member of your organisation unless you add it. Sharing the destination folder with that email address is not optional. Skip it and you land in one of two failure modes:
  • You set a folder_id the service account cannot see. Drive does not distinguish “not shared with you” from “does not exist”, so every upload fails with a 404 on a folder that is plainly sitting in your browser.
  • You left folder_id empty, so Tornado targets the service account’s own root Drive. That Drive belongs to a robot identity with no web UI you can log into, and it has no storage quota, so uploads fail with storageQuotaExceeded against a destination you cannot inspect.

Finding the service account’s email address

It is the client_email field inside the JSON key file you downloaded. It always ends in .iam.gserviceaccount.com:
In the dashboard, paste the JSON into the Service account key (JSON) box first. The UI parses client_email out of it and shows a Share your folder with: row with a copy button, so you never have to open the file by hand.
Share the folder with that address as Editor, not Viewer or Commenter. Editor is the level that lets a collaborator “add files to the folder”.
Drive has no policy document. What you grant is one OAuth scope plus one sharing level, and Tornado picks the scope for you, so the only decision you make is the sharing level.

Minimum

Those cover everything Tornado calls: files.create (the resumable upload and its chunks), files.list (finding a delivered file by name, and the one-item validation call), and files.delete.
Contributor is not enough on a Shared Drive. Google’s own guidance is explicit that if you want members who cannot remove content, “give them Contributor, Commenter, or Viewer access only. These members can’t delete files.” Tornado deletes, so Contributor breaks the cleanup path. Content manager is the lowest role that both uploads and deletes.
The minimum sharing level is the recommendation. Content manager is already the lowest role that works, and Manager adds only member management and the ability to delete the shared drive itself, neither of which Tornado touches. One thing about placement is worth a line:

Storage quota: use a Shared Drive, not My Drive

This is the single most important decision on this page, and the answer differs between My Drive and Shared Drives. Google’s Drive API documentation states it plainly:
Service accounts don’t have storage quota and can’t own any files. Instead, they must upload files and folders into shared drives, or use OAuth 2.0 to upload items on behalf of a human user.
Combine that with the ordinary Drive storage rule, “Only the files you own count toward your Google storage”, and the consequence follows:
When the service account uploads into a folder in someone’s My Drive, the service account becomes the owner of the new file. Storage is billed to the owner, and a service account has no quota to bill. Google rejects the upload with a 403:
The folder owner’s free space is irrelevant here. Their quota is not consumed because they do not own the file. Sharing the folder as Editor is necessary but not sufficient.
In a Shared Drive, the drive owns the files rather than any individual, so no personal quota is involved. Consumption counts against your Google Workspace organisation’s pooled storage. Tornado sends supportsAllDrives=true on every Drive upload, list and delete call, so Shared Drive targets work without any extra configuration on your side.Shared Drives require a work or school account. They are not available on personal @gmail.com accounts, and your Workspace edition and administrator must permit them.
Tornado’s JWT contains no sub claim, which means it does not use domain-wide delegation to impersonate a human user. The second option in Google’s guidance, “use OAuth 2.0 to upload items on behalf of a human user”, is therefore not available. A Shared Drive is the only configuration with a supported quota story.
Google Workspace caps uploads at 750 GB per day per user across My Drive and all shared drives. High-volume delivery can hit this. Nothing on the Tornado side raises it.

Setup

1. Create a Google Cloud project and enable the Drive API

1

Create the project

Go to console.cloud.google.com/projectcreate, or in the Cloud console open Menu > IAM & Admin > Create a Project.Enter a project name, optionally edit the project ID (it cannot be changed later), pick a location, then click Create.An existing project is fine. The project only carries the service account and the API enablement, it has nothing to do with where the files land.
2

Enable the Drive API

In the Cloud console go to Menu > APIs & Services > Library > Google Workspace, click Google Drive API, then click Enable.Direct link: console.cloud.google.com/apis/library/drive.googleapis.comOr with the CLI:
Skipping this step produces a 403 with accessNotConfigured on the first upload, not at configuration time.

2. Create a service account and download its JSON key

1

Create the service account

Go to console.cloud.google.com/iam-admin/serviceaccounts, select your project, click Create service account, enter a name such as tornado-drive, then click Done.Assign no IAM roles. Cloud IAM roles do not grant Drive access. Drive permissions come exclusively from Drive sharing, which is step 3.
2

Create a JSON key

Click the service account’s email address, open the Keys tab, then Add key > Create new key, select JSON, and click Create. The file downloads automatically.Google notes that “This file is the only copy of this key. After you download the key file, you cannot download it again.”
3

Note the client_email

You need this address in the next step.
The JSON key is a long-lived private credential with no expiry. Store it in a secret manager, not in your repository. Tornado stores it in Azure Key Vault and never returns it, but the copy on your disk is your responsibility. Rotate it from the same Keys tab.

3. Share the destination folder with the service account

4. Find the folder ID

Open the folder in Drive and read it out of the address bar. The ID is everything after /folders/:
For the root of a Shared Drive, the ID in the URL after /drive/folders/ is the shared drive’s own ID, which is a valid parents value.
folder_id is optional in the API and defaults to "", which means “the service account’s own root Drive”. Treat it as required. The dashboard does: its Connect & verify button stays disabled until you fill it in, precisely because an empty value delivers into an invisible Drive.

5. Configure Tornado

Storage is resolved per API key. A configuration set on one key does not apply to your other keys. Run this for every key you send jobs with.

Field reference

Characters rejected in folder_prefix:
The Drive field names differ from the GCS field names. Drive takes folder_id. GCS takes project_id and bucket. Drive accepts neither project_id nor bucket, and passing them changes nothing, because unknown fields are dropped silently rather than rejected. If you copied a working GCS payload and only swapped the URL, you have almost certainly configured the service account root Drive with an empty folder_id.

Through the dashboard

The dashboard’s Drive tile is blocked by limitation 1 above, so this is recorded for completeness rather than as a working path. The wire format nests the config under the provider key:
The dashboard labels map to the API fields as Service account key (JSON) to service_account_json and Destination folder ID to folder_id. service_account_json is capped at 10,000 characters on this route. A standard Google key file is roughly 2.3 KB, so this only bites if you paste something other than the key.

What “verified” actually proves

POST /user/gdrive validates the credentials live before saving, and the dashboard button says Connect & verify. Be precise about what that check covers. The validation mints an OAuth token from the JSON key, then issues a one-item files.list. That proves the service account exists, the private key parses and signs, the Drive API is enabled on the project, and the drive.file scope is granted. It does not touch folder_id. The list call carries no folder filter, and under drive.file a fresh service account has created nothing, so it returns an empty list and the check passes. Nothing is written. The dashboard’s S3 probe was extended to write, read back, open and abort a multipart upload, and delete; Drive validation is a single read-only files.list and was not changed. If you have read the AWS S3 page, do not carry over what it says about the probe.
A green “verified” result does not prove that the folder ID is correct, that the folder exists, that it is shared with the service account, or that the target has usable storage quota. All four fail for the first time on your first real delivery. Always follow configuration with a test job.
Then open the folder in Drive and confirm the file is there.

Troubleshooting

The job says Completed but nothing appears in my folder

Work through these in order.
1

Check which API key sent the job

Storage is per key. If the job went out on a key with no Drive config, the file went to Tornado’s managed storage and the job still reports Completed. This is the most common cause. Re-run POST /user/gdrive with the x-api-key you actually send jobs with.
2

Check that folder_id was not empty

An empty or omitted folder_id sends everything to the service account’s own root Drive, which no human can browse. There is no read-back endpoint for Drive configuration, so if you are unsure, simply re-run POST /user/gdrive with the correct folder_id. It overwrites the previous config for that key.
3

Check the file name, not the folder tree

Drive does not create nested folders. If you set base_folder or folder_prefix, look for a flat file literally named videos/my-video.mp4 inside the destination folder, not for a videos subfolder.
4

Check that you are looking at the right Drive

A shared drive and a My Drive folder can have the same name. Confirm the ID in the URL matches the folder_id you configured.

Permission and lookup errors

Quota errors

The s3_url returned for a Drive delivery is a permanent https://drive.google.com/file/d/{id}/view link, not a presigned URL. It does not expire, and anyone opening it needs Drive permission on that file. Sharing the link with someone outside the folder’s access list will show them a request-access page. Tornado resolves that link by looking the file up by name inside the folder. Two jobs that produce identical file names will collide, and the lookup returns whichever one Drive answers with first. The same applies to DELETE. If that is a risk for you, set a distinct per-job folder value on POST /jobs, which is folded into the file name and keeps the names unique.

Removing the configuration

New downloads on that key revert to Tornado’s managed storage. Files already delivered to Drive are left in place. Revoking the service account’s access in Drive, or deleting the JSON key in Cloud IAM, does not remove the stored configuration. Jobs will keep trying and failing until you run the DELETE.

Custom Storage

S3, Azure Blob, GCS and Alibaba OSS configuration

POST /user/gdrive

Full endpoint reference