Configure Google Cloud Storage
curl --request POST \
--url https://api.tornadoapi.io/user/gcs \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"project_id": "<string>",
"bucket": "<string>",
"service_account_json": "<string>",
"folder_prefix": "<string>",
"base_folder": "<string>"
}
'import requests
url = "https://api.tornadoapi.io/user/gcs"
payload = {
"project_id": "<string>",
"bucket": "<string>",
"service_account_json": "<string>",
"folder_prefix": "<string>",
"base_folder": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
project_id: '<string>',
bucket: '<string>',
service_account_json: '<string>',
folder_prefix: '<string>',
base_folder: '<string>'
})
};
fetch('https://api.tornadoapi.io/user/gcs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.tornadoapi.io/user/gcs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'project_id' => '<string>',
'bucket' => '<string>',
'service_account_json' => '<string>',
'folder_prefix' => '<string>',
'base_folder' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.tornadoapi.io/user/gcs"
payload := strings.NewReader("{\n \"project_id\": \"<string>\",\n \"bucket\": \"<string>\",\n \"service_account_json\": \"<string>\",\n \"folder_prefix\": \"<string>\",\n \"base_folder\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.tornadoapi.io/user/gcs")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"project_id\": \"<string>\",\n \"bucket\": \"<string>\",\n \"service_account_json\": \"<string>\",\n \"folder_prefix\": \"<string>\",\n \"base_folder\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tornadoapi.io/user/gcs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"project_id\": \"<string>\",\n \"bucket\": \"<string>\",\n \"service_account_json\": \"<string>\",\n \"folder_prefix\": \"<string>\",\n \"base_folder\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"message": "Google Cloud Storage configured successfully",
"provider": "gcs",
"container_or_bucket": "tornado-downloads"
}
User
Configure Google Cloud Storage
Configure Google Cloud Storage for your downloads
POST
/
user
/
gcs
Configure Google Cloud Storage
curl --request POST \
--url https://api.tornadoapi.io/user/gcs \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"project_id": "<string>",
"bucket": "<string>",
"service_account_json": "<string>",
"folder_prefix": "<string>",
"base_folder": "<string>"
}
'import requests
url = "https://api.tornadoapi.io/user/gcs"
payload = {
"project_id": "<string>",
"bucket": "<string>",
"service_account_json": "<string>",
"folder_prefix": "<string>",
"base_folder": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
project_id: '<string>',
bucket: '<string>',
service_account_json: '<string>',
folder_prefix: '<string>',
base_folder: '<string>'
})
};
fetch('https://api.tornadoapi.io/user/gcs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.tornadoapi.io/user/gcs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'project_id' => '<string>',
'bucket' => '<string>',
'service_account_json' => '<string>',
'folder_prefix' => '<string>',
'base_folder' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.tornadoapi.io/user/gcs"
payload := strings.NewReader("{\n \"project_id\": \"<string>\",\n \"bucket\": \"<string>\",\n \"service_account_json\": \"<string>\",\n \"folder_prefix\": \"<string>\",\n \"base_folder\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.tornadoapi.io/user/gcs")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"project_id\": \"<string>\",\n \"bucket\": \"<string>\",\n \"service_account_json\": \"<string>\",\n \"folder_prefix\": \"<string>\",\n \"base_folder\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tornadoapi.io/user/gcs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"project_id\": \"<string>\",\n \"bucket\": \"<string>\",\n \"service_account_json\": \"<string>\",\n \"folder_prefix\": \"<string>\",\n \"base_folder\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"message": "Google Cloud Storage configured successfully",
"provider": "gcs",
"container_or_bucket": "tornado-downloads"
}
Overview
Set up Google Cloud Storage for uploaded videos. Authenticate with a service account JSON key. Credentials are verified before saving.Header Parameters
string
required
Your API key for authentication
Request Body
string
required
Google Cloud project ID
string
required
GCS bucket name
string
required
Service account JSON credentials (as escaped string or Base64 encoded)
string
Optional folder prefix for organizing uploads (e.g.,
downloads/2024/)string
default:"videos"
Base folder name for uploaded files. Defaults to
videos if not specified. Set to a custom value to change the top-level folder where files are stored (e.g., downloads, media).Response
string
Success confirmation message
string
Storage provider type:
gcsstring
The configured bucket name
string
The configured folder prefix (if provided)
Examples
curl -X POST "https://api.tornadoapi.io/user/gcs" \
-H "x-api-key: sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"project_id": "my-gcp-project",
"bucket": "tornado-downloads",
"service_account_json": "{\"type\":\"service_account\",\"project_id\":\"my-gcp-project\",\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n\",\"client_email\":\"tornado@my-gcp-project.iam.gserviceaccount.com\"}"
}'
import requests
import json
# Load service account JSON file (recommended)
with open("service-account.json") as f:
sa_json = json.dumps(json.load(f))
response = requests.post(
"https://api.tornadoapi.io/user/gcs",
headers={"x-api-key": "sk_your_api_key"},
json={
"project_id": "my-gcp-project",
"bucket": "tornado-downloads",
"service_account_json": sa_json
}
)
print(response.json())
import requests
import json
# Build as a dict and serialize with json.dumps()
service_account = {
"type": "service_account",
"project_id": "my-gcp-project",
"private_key_id": "key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "tornado@my-gcp-project.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token"
}
response = requests.post(
"https://api.tornadoapi.io/user/gcs",
headers={"x-api-key": "sk_your_api_key"},
json={
"project_id": "my-gcp-project",
"bucket": "tornado-downloads",
"service_account_json": json.dumps(service_account)
}
)
print(response.json())
{
"message": "Google Cloud Storage configured successfully",
"provider": "gcs",
"container_or_bucket": "tornado-downloads"
}
Success Response
{
"message": "Google Cloud Storage configured successfully",
"provider": "gcs",
"container_or_bucket": "tornado-downloads"
}
Error Responses
{
"error": "Invalid service account JSON: missing 'private_key' field"
}
{
"error": "Credential validation failed: AuthenticationFailed"
}
{
"error": "Invalid API Key"
}
{
"error": "GCS storage configuration is temporarily unavailable"
}
Verification Process
When you submit storage configuration, Tornado:- Validates the request format and the service account JSON structure
- Creates a storage client with your credentials
- Attempts to upload a small test file (
verify_credentials.txt) - Deletes the test file
- If successful, saves the configuration encrypted
Required GCS Permissions
The service account needs the Storage Object Admin role, which includes:storage.objects.createstorage.objects.deletestorage.objects.getstorage.objects.list
For the
service_account_json field, you can either:- Pass the JSON as an escaped string
- Base64 encode the JSON file:
base64 -w0 service-account.json
Python users: Do not manually construct the
service_account_json as a raw string with escaped quotes.
The \n characters in the private key will be interpreted as literal newlines by Python, which produces
invalid JSON and causes a "Invalid JSON in service account credentials" error.Always use json.dumps() on a dict or a loaded JSON file to ensure proper escaping:# Correct: json.dumps() handles escaping
sa_json = json.dumps({"type": "service_account", "private_key": "-----BEGIN...\n...", ...})
# Wrong: manual string with \n becomes literal newlines
sa_json = '{"type": "service_account", "private_key": "-----BEGIN...\n..."}'
Was this page helpful?
⌘I
