API Example: Backup All Organizations
This script logs in as a GroveStreams user and backs up every organization the user belongs to, one .zip file per org. It extends the single-org backup example. An api_key cannot be used since content rights are restricted to user credentials only.
The organization list comes directly from the login response — no second API call is required to enumerate orgs. A single session token is reused across every backup.
Scope: the login response returns every organization the user is a member of, regardless of role (owner, admin, regular user, viewer). The backup endpoint requires the
MANAGE_EXPORTS capability, so orgs where the user has only viewer-level rights will fail per-org with an HTTP error. The script catches these, lists them in the final summary, and continues with the remaining orgs.Usage: edit the
userId / userPwd / backup_dir in main(), or pass them as arguments:python3 gs_backup_all_orgs.py user@example.com 'password' /home/gs/gs_backups
#!/usr/bin/env python3
# GroveStreams.com Python 3 Example
# Logs in as a GS user and backs up every organization the user belongs to.
# Extension of: https://www.grovestreams.com/developers/portal/py_org_bakup.html
#
# Backups use user-credential session auth (api_key is not allowed for backup).
#
# Scope: the login response returns every org the user belongs to in any role
# (owner, admin, regular user, viewer). The backup endpoint requires the
# MANAGE_EXPORTS capability — orgs where the user has only viewer rights will
# fail per-org with an HTTP error. Failures are reported in the final summary
# and the script continues with the remaining orgs.
#
# License:
# Copyright 2026 GroveStreams LLC.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
import datetime
import os
import re
import sys
import requests
BASE_URL = "https://grovestreams.com/api"
CHUNK_SIZE = 100_000
def check_response(response):
if response.status_code not in (200, 201):
body = response.content.decode("utf-8", errors="replace")
if response.reason:
raise Exception("HTTP Failure Reason: " + response.reason + " body: " + body)
raise Exception("HTTP Failure Body: " + body)
def login(user_id, user_pwd):
url = BASE_URL + "/login"
response = requests.post(url, json={"email": user_id, "password": user_pwd})
check_response(response)
jr = response.json()
if not jr.get("success", False):
raise Exception(str(jr.get("message", "login failed")))
session_uid = jr["sessionUid"]
# The login response embeds an "organization" array — one entry per org the
# user belongs to. Each entry has uid, type, and name (description key).
orgs = jr.get("organization", [])
return session_uid, orgs
def sanitize_filename(name):
safe = re.sub(r"[^\w.\-]+", "_", name).strip("_")
return safe or "org"
def backup_org(session_uid, org_uid, org_name, backup_dir):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M")
filename = "gs_backup_{}_{}_{}.zip".format(
sanitize_filename(org_name), org_uid[:8], timestamp
)
path = os.path.join(backup_dir, filename)
url = (
BASE_URL
+ "/organization/backup?org=" + org_uid
+ "&time=&exportAll=true&compDir=&contentDir=&session=" + session_uid
)
response = requests.get(url, stream=True)
check_response(response)
bytes_written = 0
with open(path, "wb") as fd:
for chunk in response.iter_content(CHUNK_SIZE):
fd.write(chunk)
bytes_written += len(chunk)
return path, bytes_written
def main():
# CHANGE THESE !!!!!!
user_id = "brucelee@acme.com"
user_pwd = "wingchung"
backup_dir = "/home/gs/gs_backups" # Linux path — adjust for Windows if needed
# Allow CLI override: python gs_backup_all_orgs.py <email> <password> [backup_dir]
if len(sys.argv) >= 3:
user_id = sys.argv[1]
user_pwd = sys.argv[2]
if len(sys.argv) >= 4:
backup_dir = sys.argv[3]
os.makedirs(backup_dir, exist_ok=True)
print("Logging in as {} ...".format(user_id))
session_uid, orgs = login(user_id, user_pwd)
if not orgs:
print("No organizations found for this user.")
return
print("Found {} organization(s). Backing up to {}\n".format(len(orgs), backup_dir))
failures = []
for i, org in enumerate(orgs, start=1):
org_uid = org["uid"]
org_name = org.get("name", "")
print("[{}/{}] {} ({}) ...".format(i, len(orgs), org_name, org_uid), end=" ", flush=True)
try:
path, size = backup_org(session_uid, org_uid, org_name, backup_dir)
print("OK {:,} bytes -> {}".format(size, path))
except Exception as e:
print("FAILED: {}".format(e))
failures.append((org_uid, org_name, str(e)))
print("\nDone. {} succeeded, {} failed.".format(len(orgs) - len(failures), len(failures)))
if failures:
print("Failures:")
for org_uid, org_name, err in failures:
print(" {} ({}): {}".format(org_name, org_uid, err))
sys.exit(1)
if __name__ == "__main__":
try:
main()
except Exception as e:
print(str(e))
sys.exit(1)
