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)