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

# Migration zu v5

> Was sich zwischen squarecloud-api v4 und v5 geändert hat: ein synchroner Client mit await-Fassade, einfache Dicts statt Dataclasses, nach Ressource gruppierte Methoden, eine Exception-Klasse. Eine Tabelle Methode für Methode.

v5 ist eine Neuentwicklung. Das SDK ist jetzt standardmäßig synchron (mit einer `await`-Fassade), hat keine Abhängigkeiten, gruppiert Methoden nach Ressource, gibt einfache Dicts (`TypedDict`) zurück und wirft einen einzigen Exception-Typ. Es deckt alle 67 Operationen der Square Cloud API ab.

## Auf einen Blick

| v4                                                                                          | v5                                                                                                                                                              |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `await client.x(...)`, nur asynchron                                                        | `client.group.x(...)` oder `await async_client.group.x(...)`                                                                                                    |
| Eingefrorene Dataclasses (`status.ram`)                                                     | `TypedDict`, ein einfaches Dict (`status['ram']`); unbekannte Felder bleiben erhalten, statt einen `TypeError` zu werfen                                        |
| `Application`-Objekte (`app.start()`, `app.cache`)                                          | Einfache Daten plus IDs: `client.apps.start(app_id)`. Es gibt keinen Cache                                                                                      |
| \~25 Exception-Klassen (`NotFoundError`, `TooManyRequests`, `FewMemory`, ...)               | `SquareCloudAPIError` mit `.status`, `.code`, `.message`, `.method`, `.path` (`'/v2/...'`), `.cause`                                                            |
| `squarecloud.File(path)`                                                                    | Übergib direkt einen Pfad, `bytes` oder ein binäres Dateiobjekt                                                                                                 |
| Optionale Argumente als Positionsargumente                                                  | Optionale Modifikatoren sind reine Keyword-Argumente (`status(id, raw=True)`, `commit(id, file, path=...)`, `databases.create(name, type=, version=, memory=)`) |
| Request-Listener (`@client.on_request`), Capture-Listener, `avoid_listener`, `update_cache` | Entfernt. Verwende den [Logger](/de/sdks/py/client#logging) `squarecloud` (DEBUG) oder einen eigenen [`transport=`](/de/sdks/py/client#eigener-transport)       |
| Abhängigkeiten `aiohttp` und `typing-extensions`                                            | Keine                                                                                                                                                           |
| Python `>=3.13,<3.15`                                                                       | Python `>=3.11`, ohne Obergrenze                                                                                                                                |

## Konstruktion und Optionen

| v4                                                                                     | v5                                                                                                                                                              |
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `squarecloud.Client(api_key, log_level=...)`                                           | `SquareCloud(api_key, *, base_url=BASE_URL, timeout=30.0, max_retries=2, transport=None, user_agent=...)` oder `AsyncSquareCloud(...)` mit denselben Argumenten |
| `log_level=` und ein farbiger Handler, der beim Import angehängt wird                  | `logging.getLogger('squarecloud')` mit einem `NullHandler`; konfiguriere ihn selbst                                                                             |
| Standardwerte von `aiohttp` (5 Min. pro Anfrage; 90 s zwischen Realtime-Lesevorgängen) | `timeout` in Sekunden pro Socket-Operation, mit einem Mindestwert von 120 s für offen gehaltene und KI-Aufrufe; `timeout <= 0` deaktiviert jedes Timeout        |
| Keine Wiederholungen                                                                   | `max_retries` (2) nur für die sicheren Fehler; 429 wird nie wiederholt                                                                                          |
| Fest codierter `User-Agent`                                                            | `user_agent=` ersetzt den Header (Standard `squarecloud-sdk-py/<version>`)                                                                                      |
| Eine neue Session pro Anfrage                                                          | `close()` oder `with` / `async with` schließt die gepoolten Keep-Alive-Verbindungen                                                                             |

## Methode für Methode

| alt (v4 `Client`)                                                                      | neu (v5 `SquareCloud`)                                                               | Hinweise                                                                                                                                                                                                                                                                                                             |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user()`                                                                               | `account.me()['user']`                                                               | `me()` gibt auch `applications` und `databases` zurück                                                                                                                                                                                                                                                               |
| `all_apps()`                                                                           | `account.me()['applications']`                                                       |                                                                                                                                                                                                                                                                                                                      |
| `app(app_id)`                                                                          | `apps.get(app_id)`                                                                   | Jetzt `GET /apps/{id}`: funktioniert auch mit Workspace-IDs `'<appId>-<workspaceId>'`                                                                                                                                                                                                                                |
| `user_snapshots(scope)`                                                                | `account.snapshots(*, scope=None)`                                                   | `scope` ist ein reines Keyword-Argument. Die Einträge enthalten die `version_id` und die signierte `url` der API                                                                                                                                                                                                     |
| `service_status()`                                                                     | `service.status()`                                                                   |                                                                                                                                                                                                                                                                                                                      |
| `upload_app(File(path))`                                                               | `apps.create(path)`                                                                  | Streamt von der Festplatte; gibt `AppCreated` zurück (`domain` ist der vollständige Host einer Website; es gibt kein `subdomain`)                                                                                                                                                                                    |
| `delete_app(id)`                                                                       | `apps.delete(id)`                                                                    | Gibt `None` zurück                                                                                                                                                                                                                                                                                                   |
| `all_apps_status()`                                                                    | `apps.status_all(*, workspace_id=None)`                                              | Gestoppte Apps werden nicht mehr ausgelassen                                                                                                                                                                                                                                                                         |
| `app_status(id)`                                                                       | `apps.status(id, *, raw=False)`                                                      | `raw=True` (reines Keyword-Argument) gibt Zahlen zurück                                                                                                                                                                                                                                                              |
| `start_app` / `stop_app` / `restart_app`                                               | `apps.start` / `apps.stop` / `apps.restart`                                          | Geben `None` zurück                                                                                                                                                                                                                                                                                                  |
| `get_logs(id)`                                                                         | `apps.logs(id)`                                                                      | Gibt den `str` zurück                                                                                                                                                                                                                                                                                                |
| `app_metrics(id)`                                                                      | `apps.metrics(id)`                                                                   |                                                                                                                                                                                                                                                                                                                      |
| `realtime(id)` (Async-Generator jeder `data:`-Zeile, wenn möglich als JSON dekodiert)  | `apps.realtime(id)`                                                                  | Iterator von `{'event', 'data', 'id'}` mit dem rohen `data`-Text, plus `stream`/`line` bei Logs und dem zusammengeführten `status` bei Status-Ereignissen. Verbindet sich selbst neu (siehe Verhaltensänderungen); der Stream hat kein Lese-Timeout (v4 gab nach 90 s ohne Daten auf), also beende ihn mit `close()` |
| `all_domains()`                                                                        | `apps.domains()`                                                                     |                                                                                                                                                                                                                                                                                                                      |
| `load_balancers()`                                                                     | `apps.load_balancers()`                                                              |                                                                                                                                                                                                                                                                                                                      |
| `commit(id, File(path))`                                                               | `apps.commit(id, file, *, path=None, filename=None)`                                 | Reines Keyword-Argument `path` = Zielverzeichnis; `filename` benennt eine Nicht-ZIP-Datei (`bytes` standardmäßig `commit.zip`)                                                                                                                                                                                       |
| `github_integration(id, access_token)`                                                 | `apps.deploys.set_webhook(id, access_token)`                                         |                                                                                                                                                                                                                                                                                                                      |
| `last_deploys(id)`                                                                     | `apps.deploys.list(id)`                                                              | Ereignisse enthalten `source`, `branch`, `files`; ein fehlgeschlagener Deploy endet mit `state='error'` plus `code` und `message`                                                                                                                                                                                    |
| `current_app_integration(id)`                                                          | `apps.deploys.current(id)`                                                           | Gibt `{'app'?, 'webhook'?}` statt des Webhook-Strings zurück                                                                                                                                                                                                                                                         |
| `get_app_envs` / `set_app_envs` / `overwrite_app_envs` / `delete_app_envs`             | `apps.envs.get` / `set` / `replace` / `delete`                                       |                                                                                                                                                                                                                                                                                                                      |
| `clear_app_envs(id)`                                                                   | `apps.envs.replace(id, {})`                                                          |                                                                                                                                                                                                                                                                                                                      |
| `app_files_list(id, path)`                                                             | `apps.files.list(id, path=None)`                                                     | Die Einträge sind die `FileEntry` der API (ohne berechnetes `path`/`app_id`). Ein fehlendes Verzeichnis wirft 404 `FILE_NOT_FOUND`, statt `[]` zurückzugeben; ein gesperrtes ergibt 403 `BLOCKED_PATH`                                                                                                               |
| `read_app_file(id, path)` → `BytesIO`                                                  | `apps.files.read(id, path)` → `bytes`                                                | Base64-kodiert abgerufen (`?encoding=base64`) und dekodiert; über 10 MB ergibt 413 `FILE_TOO_LARGE`                                                                                                                                                                                                                  |
| `create_app_file(id, File(...), path)`                                                 | `apps.files.write(id, path, content)`                                                | Ein `str` wird als Text gesendet, `bytes` Base64-kodiert (binärsicher); der Pfad wird unverändert gesendet (kein zusätzliches `/`); leerer Inhalt (`''` oder `b''`) erstellt eine leere Datei                                                                                                                        |
| `move_app_file(id, origin, dest)`                                                      | `apps.files.move(id, path, to)`                                                      |                                                                                                                                                                                                                                                                                                                      |
| `delete_app_file(id, path)`                                                            | `apps.files.delete(id, path)`                                                        |                                                                                                                                                                                                                                                                                                                      |
| `snapshot(id)` → `Snapshot`                                                            | `apps.snapshots.create(id)`                                                          | `{'pending': True}` bei 202, statt einen Fehler zu werfen; frage dann `list` ab, erstelle nie erneut                                                                                                                                                                                                                 |
| `all_app_snapshots(id)`                                                                | `apps.snapshots.list(id)`                                                            | Die Einträge enthalten die `version_id` und die signierte Download-`url` der API, unverändert weitergereicht                                                                                                                                                                                                         |
| `restore_snapshot('app', id, snapshot_id, version_id)`                                 | `apps.snapshots.restore(id, name, version_id)`                                       | `name` und `version_id` eines `list`-Eintrags                                                                                                                                                                                                                                                                        |
| `restore_snapshot('database', id, ...)`                                                | `databases.snapshots.restore(id, name, version_id)`                                  | `name` und `version_id` eines `list`-Eintrags                                                                                                                                                                                                                                                                        |
| `Snapshot.download(path)`                                                              | `client.download_snapshot(url, dest)`                                                | Streamt das ZIP selbst (v4 verpackte es in ein weiteres ZIP)                                                                                                                                                                                                                                                         |
| `domain_analytics(id, start=, end=, ...)`                                              | `apps.network.analytics(id, start, end, *, country=, ...)`                           | `start`/`end` werden von der API verlangt; die Filter sind reine Keyword-Argumente                                                                                                                                                                                                                                   |
| `network_errors(id, start, end, include_4xx)` / `network_logs` / `network_performance` | `apps.network.errors(id, start, end, *, include_4xx=False)` / `logs` / `performance` | `analytics`, `errors` und `performance` geben für ein Fenster ohne Daten `None` zurück                                                                                                                                                                                                                               |
| `dns_records(id)`                                                                      | `apps.network.dns(id)`                                                               |                                                                                                                                                                                                                                                                                                                      |
| `set_custom_domain(id, custom_domain)`                                                 | `apps.network.set_domain(id, domain)`                                                | v4 hat den Body nie gesendet                                                                                                                                                                                                                                                                                         |
| `purge_cache(id)`                                                                      | `apps.network.purge_cache(id)`                                                       |                                                                                                                                                                                                                                                                                                                      |
| `link_github_app(id, repository_name, repository_branch)`                              | `apps.deploys.link_github_app(id, repository, branch)`                               | Funktioniert jetzt mit einem API-Schlüssel (Scope `apps:deploy`); gibt das `LinkedRepository` (`{id, full_name, branch}`) statt des rohen Dicts zurück; 403 `GITHUB_NOT_CONNECTED` ohne Installation der GitHub App                                                                                                  |
| `unlink_github_app(id)`                                                                | `apps.deploys.unlink_github_app(id)`                                                 | Gibt `None` zurück; 400 `GIT_NOT_CONFIGURED`, wenn nichts verknüpft ist                                                                                                                                                                                                                                              |
| `create_database(name, memory, type, version=None)`                                    | `databases.create(name, type=, version=, memory=)`                                   | Reine Keyword-Argumente; `version` ist Pflicht (eine Hauptversion wie `'8'` funktioniert)                                                                                                                                                                                                                            |
| `get_database_info(id)`                                                                | `databases.get(id)`                                                                  |                                                                                                                                                                                                                                                                                                                      |
| `edit_database(id, name, memory)`                                                      | `databases.update(id, name=, ram=)`                                                  | v4 sendete `memory`, was die API ignorierte                                                                                                                                                                                                                                                                          |
| `delete_database` / `start_database` / `stop_database`                                 | `databases.delete` / `start` / `stop`                                                |                                                                                                                                                                                                                                                                                                                      |
| `get_database_status(id)`                                                              | `databases.status(id, *, raw=False)`                                                 |                                                                                                                                                                                                                                                                                                                      |
| `all_databases_status()`                                                               | `databases.status_all()`                                                             |                                                                                                                                                                                                                                                                                                                      |
| `database_metrics(id)`                                                                 | `databases.metrics(id)`                                                              |                                                                                                                                                                                                                                                                                                                      |
| `get_database_certificate(id)` → `Certificate`                                         | `databases.certificate(id)` → Base64-`str`                                           | `base64.b64decode(...)` ergibt das PEM                                                                                                                                                                                                                                                                               |
| `reset_database_password(id)`                                                          | `databases.reset_credentials(id, 'password')`                                        | Gibt das neue Passwort zurück                                                                                                                                                                                                                                                                                        |
| `reset_database_certificate(id)`                                                       | `databases.reset_credentials(id, 'certificate')`                                     | Gibt `''` zurück                                                                                                                                                                                                                                                                                                     |
| `all_database_snapshots(id)` / `database_snapshot(id)`                                 | `databases.snapshots.list(id)` / `create(id)`                                        | Die Einträge enthalten `version_id` und `url`, wie bei Apps                                                                                                                                                                                                                                                          |
| `create_workspace(name)`                                                               | `workspaces.create(name)`                                                            | Gibt `{id, name}` in einer Anfrage zurück                                                                                                                                                                                                                                                                            |
| `all_workspaces()` / `get_workspace(id)`                                               | `workspaces.list()` / `workspaces.get(id)`                                           | v4 schrieb jede App-`id` in die zusammengesetzte Form `'<appId>-<workspaceId>'` um; v5 gibt die reine ID der API zurück, also baue sie selbst: `f"{app['id']}-{workspace['id']}"`                                                                                                                                    |
| `delete_workspace` / `leave_workspace`                                                 | `workspaces.delete` / `workspaces.leave`                                             |                                                                                                                                                                                                                                                                                                                      |
| `add_member_to_workspace(ws, invite_code, permissions)`                                | `workspaces.members.add(ws, code, group)`                                            |                                                                                                                                                                                                                                                                                                                      |
| `modify_member_permissions(ws, user_id, permissions)`                                  | `workspaces.members.update(ws, member_id, group)`                                    |                                                                                                                                                                                                                                                                                                                      |
| `remove_member_from_workspace(ws, user_id)`                                            | `workspaces.members.remove(ws, member_id)`                                           |                                                                                                                                                                                                                                                                                                                      |
| `get_invite_code()`                                                                    | `workspaces.members.invite_code()`                                                   |                                                                                                                                                                                                                                                                                                                      |
| `add_app_to_workspace` / `remove_app_from_workspace`                                   | `workspaces.apps.add` / `workspaces.apps.remove`                                     |                                                                                                                                                                                                                                                                                                                      |
| —                                                                                      | `ai.chat(request)`                                                                   | Neu; `request` ist der Body im OpenAI-Stil (`{'messages': [...], 'model': ...}`)                                                                                                                                                                                                                                     |
| `client.api_key`                                                                       | Entfernt                                                                             | Behalte deine eigene Referenz auf den Schlüssel                                                                                                                                                                                                                                                                      |
| `on_request(endpoint)`, `Application.capture(endpoint)`                                | Entfernt                                                                             | Siehe die Zeile zu den Listenern unter [Auf einen Blick](#auf-einen-blick)                                                                                                                                                                                                                                           |
| `squarecloud.utils.ConfigFile`                                                         | Entfernt                                                                             | Schreibe die Datei `squarecloud.app` selbst (Zeilen `KEY=value`)                                                                                                                                                                                                                                                     |

`Application`-Methoden entsprechen denselben Aufrufen mit der ID: `app.logs()` → `client.apps.logs(app.id)`, `app.files_list(path)` → `client.apps.files.list(app.id, path)` und so weiter.

## Typen

Antworten sind `TypedDict`s in `squarecloud.types`, benannt wie in den SDKs für JS und Go: `Account`, `User`, `Plan`, `AppSummary`, `DatabaseSummary`, `App`, `AppCreated`, `StatusListItem`, `RuntimeStats`, `MetricPoint`, `AppDomain`, `LoadBalancers`, `DeployEvent`, `DeployCurrent`, `DeployRepository`, `LinkedRepository`, `EnvVars`, `FileEntry`, `Snapshot`, `SnapshotCreated`, `SnapshotScope`, `AnalyticsFilters`, `NetworkAnalytics`, `NetworkErrors`, `NetworkLog`, `NetworkPerformance`, `DNSRecord`, `Database`, `DatabaseCreated`, `DatabaseType`, `Workspace`, `WorkspaceCreated`, `WorkspaceGroup`, `ServiceStatus`, `ServiceEntry`, `ChatRequest`, `ChatMessage`, `ChatCompletion`, `RealtimeEvent`, `RealtimeStatus`. Sie ersetzen die `data/*`-Dataclasses von v4 (`UserData`, `StatusData`, `AppData`, ...). `squarecloud.Response` ist jetzt das Antwortprotokoll des Transports (siehe [Eigener Transport](/de/sdks/py/client#eigener-transport)); die `Response` von v4, die Mutationen zurückgaben, gibt es nicht mehr, und sie geben `None` zurück.

## Fehler

| v4                                                               | v5                                                                                                                               |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `AuthenticationFailure`                                          | `e.status == 401` (`ACCESS_DENIED`, auch bei einem abgelaufenen Schlüssel)                                                       |
| `NotFoundError`, `ApplicationNotFound`                           | `e.status == 404` (`APP_NOT_FOUND`, `WORKSPACE_NOT_FOUND`, ...)                                                                  |
| `BadRequestError` und die `InvalidConfig`-Familie                | `e.status == 400`, prüfe `e.code`                                                                                                |
| `TooManyRequests`                                                | `e.status == 429` (`RATE_LIMITED`, `KEEP_CALM`)                                                                                  |
| `FewMemory` (nie geworfen: Die API sendet `INSUFFICIENT_MEMORY`) | `e.code == 'INSUFFICIENT_MEMORY'`                                                                                                |
| `InvalidDomain` (`REGEX_VALIDATION`, wird nicht mehr gesendet)   | `e.code == 'INVALID_DOMAIN'`                                                                                                     |
| `RequestError` bei 403/503                                       | `e.code` in `MISSING_SCOPE`, `RESOURCE_NOT_ALLOWED`, `BLOCKED_PATH`, `UPLOAD_BUSY`, ...                                          |
| Durchsickernde `aiohttp`-Exceptions                              | `e.status == 0`, `e.code` in `NETWORK_ERROR`, `TIMEOUT` (die ursprüngliche Exception in `e.cause`)                               |
| —                                                                | `e.status == 0` mit `FILE_TOO_LARGE` oder `INVALID_ID`: lokale Prüfungen, es wurde nichts gesendet                               |
| Durchsickerndes Proxy-HTML oder ein Body, der kein JSON ist      | `UNKNOWN_ERROR` mit dem echten Status und der Nachricht `HTTP <status>` (`Invalid JSON in HTTP <status> response` bei einem 2xx) |

`str(e)` lautet `'<METHOD> <path>: HTTP <status> <CODE>: <message>'`, ohne `HTTP <status>`, wenn der Status `0` ist, und ohne `: <message>`, wenn die Nachricht leer ist. `e.message` ist `''`, wenn der Server nur einen Code gesendet hat.

## Verhaltensänderungen

* Optionale Modifikatoren sind reine Keyword-Argumente: `account.snapshots(scope=)`, `apps.status_all(workspace_id=)`, `apps.status(id, raw=)`, `databases.status(id, raw=)`, `apps.commit(id, file, path=, filename=)`, `apps.network.errors(..., include_4xx=)`, die Filter von `apps.network.analytics(...)`, `databases.update(id, name=, ram=)` und `databases.create(name, type=, version=, memory=)`. Der optionale `path` von `apps.files.list` bleibt ein Positionsargument.
* Ein 2xx-Body `{"status": "error"}` wirft einen Fehler. Abgelehnte Starts/Stopps von Apps und Datenbanken ergeben 409 nur mit einem Code (`CONTAINER_ALREADY_STARTED`, `ACTION_FAILED`, ...). Ein 202 `SNAPSHOT_PROCESSING` gibt `{'pending': True}` zurück, statt einen Fehler zu werfen: Frage `list` ab, rufe `create` nie erneut auf.
* Nicht gesetzte optionale Query-Werte (und `''`) werden weggelassen, statt gesendet zu werden.
* String-Ergebnisse sind nie `None`: `reset_credentials(id, 'certificate')` und ein entfernter Webhook geben `''` zurück.
* `apps.files.write` sendet einen `str` als Text und `bytes` Base64-kodiert; leerer Inhalt erstellt eine leere Datei; Inhalt über 1 MiB wird ohne Timeout gesendet. `apps.files.read` fordert immer Base64 an und gibt die dekodierten `bytes` zurück.
* `apps.files.list` eines fehlenden Verzeichnisses wirft 404 `FILE_NOT_FOUND`.
* 503 `DATABASE_UNAVAILABLE` wird nur bei `GET` wiederholt, da es auftreten kann, nachdem eine Mutation angewendet wurde; das Wiederholen einer idempotenten Mutation liegt beim Aufrufer.
* Der Realtime-Stream liefert Ereignisse `{'event', 'data', 'id', ...}`, öffnet sich höchstens 3 Mal hintereinander neu, mit einem Öffnen pro 5,5 s, und wirft, wenn ein Öffnen fehlschlägt.

## Async

v4 war nur asynchron. In v5 ist `SquareCloud` synchron und `AsyncSquareCloud` die `await`-Fassade: dieselben Gruppen und Methoden, jeder Aufruf läuft in `asyncio.to_thread`, sodass die Event Loop nie blockiert wird. Der Realtime-Stream wird zu `async for` (ein Lese-Thread versorgt die Loop); schließe ihn mit `async with` oder `close()`.

v4:

```python theme={"system"}
client = squarecloud.Client(key)
status = await client.app_status(app_id)
print(status.ram)
async for data in client.realtime(app_id):
    print(data)
```

v5:

```python theme={"system"}
async with squarecloud.AsyncSquareCloud(key) as client:
    status = await client.apps.status(app_id)
    print(status['ram'])
    async with client.apps.realtime(app_id) as stream:
        async for event in stream:
            print(event['data'])
```
