> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aigenda.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Full example

> A Python script: sign-in, minute check, upload, processing, protocol saved to a file.

The script uploads a file to your personal space, waits for processing, and saves the protocol next to the file. You need Python 3.9+ and the `requests` package.

```bash theme={null}
pip install requests
export AIGENDA_EMAIL="you@example.com"
export AIGENDA_PASSWORD="your-password"
python aigenda_upload.py "Sales team standup.mp3" audio/mpeg
```

```python aigenda_upload.py theme={null}
"""Upload a recording to AiGenda and get the protocol.

Usage: python aigenda_upload.py <file> <content_type>
"""
import math
import os
import sys
import time

import requests

API = "https://api.aigenda.tech"
MIN_RESERVE_SECONDS = 5 * 60  # don't start if almost no minutes are left


def login(email: str, password: str) -> str:
    for attempt in range(3):
        r = requests.post(f"{API}/api/token/", json={"username": email, "password": password}, timeout=30)
        if r.status_code == 401:
            raise SystemExit("Wrong email or password")
        if r.ok and r.headers.get("Content-Type", "").startswith("application/json"):
            return r.json()["access"]
        time.sleep(1.5)
    r.raise_for_status()
    raise RuntimeError("Could not sign in")


def main_space(headers: dict) -> dict:
    r = requests.get(f"{API}/api/spaces/", params={"limit": 50, "offset": 0}, headers=headers, timeout=30)
    r.raise_for_status()
    return next(item["space"] for item in r.json()["results"] if item["is_main_space"])


def available_seconds(headers: dict, space_id: int) -> int:
    r = requests.get(f"{API}/api/spaces/{space_id}/", headers=headers, timeout=30)
    r.raise_for_status()
    return r.json()["subscribe"]["available_usage_count"]


def upload_file(headers: dict, space_id: int, path: str, content_type: str) -> int:
    size = os.path.getsize(path)
    r = requests.post(
        f"{API}/api/spaces/{space_id}/audio/",
        headers=headers,
        json={"title": os.path.basename(path), "size": size, "content_type": content_type},
        timeout=30,
    )
    r.raise_for_status()
    audio = r.json()

    part_size = math.ceil(size / len(audio["url"]))
    parts = []
    with open(path, "rb") as f:
        for number, url in enumerate(audio["url"], start=1):
            put = requests.put(url, data=f.read(part_size), timeout=900)
            put.raise_for_status()
            parts.append({"PartNumber": number, "ETag": put.headers["ETag"]})

    r = requests.post(
        f"{API}/api/spaces/{space_id}/audio/{audio['id']}/complete_upload/",
        headers=headers,
        json={"parts": parts},
        timeout=30,
    )
    r.raise_for_status()

    for _ in range(60):
        info = requests.get(f"{API}/api/spaces/{space_id}/audio/{audio['id']}/info/", headers=headers, timeout=30)
        if info.ok and info.json().get("is_uploaded"):
            return audio["id"]
        time.sleep(2)
    raise TimeoutError("AiGenda did not confirm the file within 2 minutes")


def start_processing(headers: dict, audio_id: int) -> None:
    r = requests.post(f"{API}/api/audio/classify/", headers=headers, json={"audio_id": audio_id}, timeout=30)
    r.raise_for_status()


def wait_for_result(headers: dict, space_id: int, audio_id: int, timeout: int = 3600) -> dict:
    url = f"{API}/api/spaces/{space_id}/audio/{audio_id}/result/"
    started = time.monotonic()
    not_found_since = failing_since = None
    while time.monotonic() - started < timeout:
        r = requests.get(url, headers=headers, timeout=30)
        now = time.monotonic()
        if r.status_code == 200:
            not_found_since = failing_since = None
            body = r.json()
            if body.get("classified"):
                if body.get("success"):
                    return body
                raise RuntimeError("Processing failed, the minutes will be returned automatically")
        elif r.status_code == 404:
            not_found_since = not_found_since or now
            if now - not_found_since > 60:
                raise RuntimeError("Processing not started")
        elif r.status_code >= 500:
            failing_since = failing_since or now
            if now - failing_since > 180:
                raise RuntimeError("Processing failed, the minutes will be returned automatically")
        time.sleep(15)
    raise TimeoutError("The result was not ready within an hour")


def main() -> None:
    path, content_type = sys.argv[1], sys.argv[2]
    token = login(os.environ["AIGENDA_EMAIL"], os.environ["AIGENDA_PASSWORD"])
    headers = {"Authorization": f"Bearer {token}"}

    space = main_space(headers)
    left = available_seconds(headers, space["id"])
    print(f"Space \"{space['name']}\", {left // 60} min left")
    if left <= MIN_RESERVE_SECONDS:
        raise SystemExit("Almost no minutes left on the plan")

    audio_id = upload_file(headers, space["id"], path, content_type)
    print(f"File uploaded, audio_id={audio_id}")

    start_processing(headers, audio_id)
    print("Processing started, waiting for the result…")

    result = wait_for_result(headers, space["id"], audio_id)
    out = os.path.splitext(path)[0] + ".protocol.md"
    with open(out, "w", encoding="utf-8") as f:
        f.write(result["result"]["protocol"])
    print(f"Protocol saved: {out}")


if __name__ == "__main__":
    main()
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.