> ## 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.

# Migrer vers la v5

> Ce qui a changé entre squarecloud-api v4 et v5 : un client synchrone avec une façade await, des dicts simples au lieu de dataclasses, des méthodes regroupées par ressource, une seule classe d'exception. Un tableau méthode par méthode.

La v5 est une réécriture. Le SDK est désormais synchrone par défaut (avec une façade `await`), n'a aucune dépendance, regroupe les méthodes par ressource, renvoie des dicts simples (`TypedDict`) et lève un seul type d'exception. Il couvre les 67 opérations de l'API Square Cloud.

## En un coup d'œil

| v4                                                                                                   | v5                                                                                                                                                                    |
| ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `await client.x(...)`, asynchrone uniquement                                                         | `client.group.x(...)`, ou `await async_client.group.x(...)`                                                                                                           |
| Dataclasses figées (`status.ram`)                                                                    | `TypedDict`, un dict simple (`status['ram']`) ; les champs inconnus sont conservés au lieu de lever une `TypeError`                                                   |
| Objets `Application` (`app.start()`, `app.cache`)                                                    | Des données brutes plus des identifiants : `client.apps.start(app_id)`. Il n'y a pas de cache                                                                         |
| \~25 classes d'exception (`NotFoundError`, `TooManyRequests`, `FewMemory`, ...)                      | `SquareCloudAPIError` avec `.status`, `.code`, `.message`, `.method`, `.path` (`'/v2/...'`), `.cause`                                                                 |
| `squarecloud.File(path)`                                                                             | Passez directement un chemin, des `bytes` ou un objet fichier binaire                                                                                                 |
| Arguments optionnels par position                                                                    | Les modificateurs optionnels sont uniquement nommés (`status(id, raw=True)`, `commit(id, file, path=...)`, `databases.create(name, type=, version=, memory=)`)        |
| Listeners de requêtes (`@client.on_request`), listeners de capture, `avoid_listener`, `update_cache` | Supprimés. Utilisez le [logger](/fr/sdks/py/client#journalisation) `squarecloud` (DEBUG) ou un [`transport=`](/fr/sdks/py/client#transport-personnalisé) personnalisé |
| Dépendances `aiohttp` et `typing-extensions`                                                         | Aucune                                                                                                                                                                |
| Python `>=3.13,<3.15`                                                                                | Python `>=3.11`, sans borne supérieure                                                                                                                                |

## Construction et options

| 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=...)`, ou `AsyncSquareCloud(...)` avec les mêmes arguments  |
| `log_level=` et un handler coloré attaché à l'import                                     | `logging.getLogger('squarecloud')` avec un `NullHandler` ; configurez-le vous-même                                                                              |
| Valeurs par défaut d'`aiohttp` (5 min par requête ; 90 s entre deux lectures temps réel) | `timeout` en secondes par opération de socket, avec un plancher de 120 s pour les appels maintenus ouverts et l'IA ; `timeout <= 0` désactive tous les timeouts |
| Aucune nouvelle tentative                                                                | `max_retries` (2) pour les seuls échecs sans risque ; un 429 n'est jamais réessayé                                                                              |
| `User-Agent` codé en dur                                                                 | `user_agent=` remplace l'en-tête (par défaut `squarecloud-sdk-py/<version>`)                                                                                    |
| Une nouvelle session par requête                                                         | `close()` ou `with` / `async with` ferme les connexions keep-alive du pool                                                                                      |

## Méthode par méthode

| ancien (v4 `Client`)                                                                        | nouveau (v5 `SquareCloud`)                                                           | remarques                                                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user()`                                                                                    | `account.me()['user']`                                                               | `me()` renvoie aussi `applications` et `databases`                                                                                                                                                                                                                                                                                          |
| `all_apps()`                                                                                | `account.me()['applications']`                                                       |                                                                                                                                                                                                                                                                                                                                             |
| `app(app_id)`                                                                               | `apps.get(app_id)`                                                                   | Désormais `GET /apps/{id}` : fonctionne avec les identifiants de workspace `'<appId>-<workspaceId>'`                                                                                                                                                                                                                                        |
| `user_snapshots(scope)`                                                                     | `account.snapshots(*, scope=None)`                                                   | `scope` est uniquement nommé. Les éléments portent le `version_id` et l'`url` signée de l'API                                                                                                                                                                                                                                               |
| `service_status()`                                                                          | `service.status()`                                                                   |                                                                                                                                                                                                                                                                                                                                             |
| `upload_app(File(path))`                                                                    | `apps.create(path)`                                                                  | Diffusé en flux depuis le disque ; renvoie `AppCreated` (`domain` est l'hôte complet d'un site ; il n'y a pas de `subdomain`)                                                                                                                                                                                                               |
| `delete_app(id)`                                                                            | `apps.delete(id)`                                                                    | Renvoie `None`                                                                                                                                                                                                                                                                                                                              |
| `all_apps_status()`                                                                         | `apps.status_all(*, workspace_id=None)`                                              | Les applications arrêtées ne sont plus omises                                                                                                                                                                                                                                                                                               |
| `app_status(id)`                                                                            | `apps.status(id, *, raw=False)`                                                      | `raw=True` (uniquement nommé) renvoie des nombres                                                                                                                                                                                                                                                                                           |
| `start_app` / `stop_app` / `restart_app`                                                    | `apps.start` / `apps.stop` / `apps.restart`                                          | Renvoient `None`                                                                                                                                                                                                                                                                                                                            |
| `get_logs(id)`                                                                              | `apps.logs(id)`                                                                      | Renvoie la `str`                                                                                                                                                                                                                                                                                                                            |
| `app_metrics(id)`                                                                           | `apps.metrics(id)`                                                                   |                                                                                                                                                                                                                                                                                                                                             |
| `realtime(id)` (générateur asynchrone de chaque ligne `data:`, décodée en JSON si possible) | `apps.realtime(id)`                                                                  | Itérateur de `{'event', 'data', 'id'}` avec le texte brut de `data`, plus `stream`/`line` sur les logs et le `status` fusionné sur les événements de statut. Se reconnecte tout seul (voir Changements de comportement) ; le flux n'a pas de timeout de lecture (la v4 abandonnait après 90 s sans données), arrêtez-le donc avec `close()` |
| `all_domains()`                                                                             | `apps.domains()`                                                                     |                                                                                                                                                                                                                                                                                                                                             |
| `load_balancers()`                                                                          | `apps.load_balancers()`                                                              |                                                                                                                                                                                                                                                                                                                                             |
| `commit(id, File(path))`                                                                    | `apps.commit(id, file, *, path=None, filename=None)`                                 | `path` uniquement nommé = répertoire de destination ; `filename` nomme un fichier non zip (les `bytes` prennent par défaut `commit.zip`)                                                                                                                                                                                                    |
| `github_integration(id, access_token)`                                                      | `apps.deploys.set_webhook(id, access_token)`                                         |                                                                                                                                                                                                                                                                                                                                             |
| `last_deploys(id)`                                                                          | `apps.deploys.list(id)`                                                              | Les événements incluent `source`, `branch`, `files` ; un deploy échoué se termine par `state='error'` plus `code` et `message`                                                                                                                                                                                                              |
| `current_app_integration(id)`                                                               | `apps.deploys.current(id)`                                                           | Renvoie `{'app'?, 'webhook'?}` au lieu de la chaîne du webhook                                                                                                                                                                                                                                                                              |
| `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)`                                                     | Les entrées sont les `FileEntry` de l'API (sans `path`/`app_id` calculés). Un répertoire inexistant lève 404 `FILE_NOT_FOUND` au lieu de renvoyer `[]` ; un répertoire bloqué donne 403 `BLOCKED_PATH`                                                                                                                                      |
| `read_app_file(id, path)` → `BytesIO`                                                       | `apps.files.read(id, path)` → `bytes`                                                | Récupéré encodé en base64 (`?encoding=base64`) puis décodé ; au-delà de 10 Mo, donne 413 `FILE_TOO_LARGE`                                                                                                                                                                                                                                   |
| `create_app_file(id, File(...), path)`                                                      | `apps.files.write(id, path, content)`                                                | Une `str` est envoyée en texte, des `bytes` encodés en base64 (compatible binaire) ; chemin envoyé tel quel (sans `/` supplémentaire) ; un contenu vide (`''` ou `b''`) crée un fichier vide                                                                                                                                                |
| `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}` sur un 202 au lieu de lever une erreur ; interrogez ensuite `list`, ne recréez jamais                                                                                                                                                                                                                                   |
| `all_app_snapshots(id)`                                                                     | `apps.snapshots.list(id)`                                                            | Les éléments portent le `version_id` et l'`url` de téléchargement signée de l'API, transmis tels quels                                                                                                                                                                                                                                      |
| `restore_snapshot('app', id, snapshot_id, version_id)`                                      | `apps.snapshots.restore(id, name, version_id)`                                       | `name` et `version_id` d'un élément de `list`                                                                                                                                                                                                                                                                                               |
| `restore_snapshot('database', id, ...)`                                                     | `databases.snapshots.restore(id, name, version_id)`                                  | `name` et `version_id` d'un élément de `list`                                                                                                                                                                                                                                                                                               |
| `Snapshot.download(path)`                                                                   | `client.download_snapshot(url, dest)`                                                | Diffuse le zip lui-même (la v4 l'enveloppait dans un autre zip)                                                                                                                                                                                                                                                                             |
| `domain_analytics(id, start=, end=, ...)`                                                   | `apps.network.analytics(id, start, end, *, country=, ...)`                           | `start`/`end` sont exigés par l'API ; les filtres sont uniquement nommés                                                                                                                                                                                                                                                                    |
| `network_errors(id, start, end, include_4xx)` / `network_logs` / `network_performance`      | `apps.network.errors(id, start, end, *, include_4xx=False)` / `logs` / `performance` | `analytics`, `errors` et `performance` renvoient `None` pour une fenêtre sans données                                                                                                                                                                                                                                                       |
| `dns_records(id)`                                                                           | `apps.network.dns(id)`                                                               |                                                                                                                                                                                                                                                                                                                                             |
| `set_custom_domain(id, custom_domain)`                                                      | `apps.network.set_domain(id, domain)`                                                | La v4 n'envoyait jamais le corps                                                                                                                                                                                                                                                                                                            |
| `purge_cache(id)`                                                                           | `apps.network.purge_cache(id)`                                                       |                                                                                                                                                                                                                                                                                                                                             |
| `link_github_app(id, repository_name, repository_branch)`                                   | `apps.deploys.link_github_app(id, repository, branch)`                               | Fonctionne désormais avec une clé API (scope `apps:deploy`) ; renvoie le `LinkedRepository` (`{id, full_name, branch}`) au lieu du dict brut ; 403 `GITHUB_NOT_CONNECTED` sans installation de la GitHub App                                                                                                                                |
| `unlink_github_app(id)`                                                                     | `apps.deploys.unlink_github_app(id)`                                                 | Renvoie `None` ; 400 `GIT_NOT_CONFIGURED` quand rien n'est lié                                                                                                                                                                                                                                                                              |
| `create_database(name, memory, type, version=None)`                                         | `databases.create(name, type=, version=, memory=)`                                   | Uniquement nommés ; `version` est obligatoire (une version majeure comme `'8'` fonctionne)                                                                                                                                                                                                                                                  |
| `get_database_info(id)`                                                                     | `databases.get(id)`                                                                  |                                                                                                                                                                                                                                                                                                                                             |
| `edit_database(id, name, memory)`                                                           | `databases.update(id, name=, ram=)`                                                  | La v4 envoyait `memory`, que l'API ignorait                                                                                                                                                                                                                                                                                                 |
| `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)` → `str` en base64                                        | `base64.b64decode(...)` donne le PEM                                                                                                                                                                                                                                                                                                        |
| `reset_database_password(id)`                                                               | `databases.reset_credentials(id, 'password')`                                        | Renvoie le nouveau mot de passe                                                                                                                                                                                                                                                                                                             |
| `reset_database_certificate(id)`                                                            | `databases.reset_credentials(id, 'certificate')`                                     | Renvoie `''`                                                                                                                                                                                                                                                                                                                                |
| `all_database_snapshots(id)` / `database_snapshot(id)`                                      | `databases.snapshots.list(id)` / `create(id)`                                        | Les éléments portent `version_id` et `url`, comme pour les applications                                                                                                                                                                                                                                                                     |
| `create_workspace(name)`                                                                    | `workspaces.create(name)`                                                            | Renvoie `{id, name}` en une seule requête                                                                                                                                                                                                                                                                                                   |
| `all_workspaces()` / `get_workspace(id)`                                                    | `workspaces.list()` / `workspaces.get(id)`                                           | La v4 réécrivait l'`id` de chaque application sous la forme composite `'<appId>-<workspaceId>'` ; la v5 renvoie l'identifiant brut de l'API, construisez-le donc vous-même : `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)`                                                                   | Nouveau ; `request` est le corps de style OpenAI (`{'messages': [...], 'model': ...}`)                                                                                                                                                                                                                                                      |
| `client.api_key`                                                                            | Supprimé                                                                             | Gardez votre propre référence à la clé                                                                                                                                                                                                                                                                                                      |
| `on_request(endpoint)`, `Application.capture(endpoint)`                                     | Supprimés                                                                            | Voir la ligne des listeners dans [En un coup d'œil](#en-un-coup-dœil)                                                                                                                                                                                                                                                                       |
| `squarecloud.utils.ConfigFile`                                                              | Supprimé                                                                             | Écrivez vous-même le fichier `squarecloud.app` (lignes `KEY=value`)                                                                                                                                                                                                                                                                         |

Les méthodes d'`Application` correspondent aux mêmes appels avec l'identifiant : `app.logs()` → `client.apps.logs(app.id)`, `app.files_list(path)` → `client.apps.files.list(app.id, path)`, et ainsi de suite.

## Types

Les réponses sont des `TypedDict` de `squarecloud.types`, nommés comme dans les SDK JS et 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`. Ils remplacent les dataclasses `data/*` de la v4 (`UserData`, `StatusData`, `AppData`, ...). `squarecloud.Response` est désormais le protocole de réponse du transport (voir [Transport personnalisé](/fr/sdks/py/client#transport-personnalisé)) ; le `Response` de la v4 que renvoyaient les mutations a disparu, et celles-ci renvoient `None`.

## Erreurs

| v4                                                              | v5                                                                                                                      |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `AuthenticationFailure`                                         | `e.status == 401` (`ACCESS_DENIED`, y compris pour une clé expirée)                                                     |
| `NotFoundError`, `ApplicationNotFound`                          | `e.status == 404` (`APP_NOT_FOUND`, `WORKSPACE_NOT_FOUND`, ...)                                                         |
| `BadRequestError` et la famille `InvalidConfig`                 | `e.status == 400`, vérifiez `e.code`                                                                                    |
| `TooManyRequests`                                               | `e.status == 429` (`RATE_LIMITED`, `KEEP_CALM`)                                                                         |
| `FewMemory` (jamais levée : l'API envoie `INSUFFICIENT_MEMORY`) | `e.code == 'INSUFFICIENT_MEMORY'`                                                                                       |
| `InvalidDomain` (`REGEX_VALIDATION`, qui n'est plus envoyé)     | `e.code == 'INVALID_DOMAIN'`                                                                                            |
| `RequestError` pour 403/503                                     | `e.code` parmi `MISSING_SCOPE`, `RESOURCE_NOT_ALLOWED`, `BLOCKED_PATH`, `UPLOAD_BUSY`, ...                              |
| Exceptions `aiohttp` qui s'échappent                            | `e.status == 0`, `e.code` parmi `NETWORK_ERROR`, `TIMEOUT` (exception d'origine dans `e.cause`)                         |
| —                                                               | `e.status == 0` avec `FILE_TOO_LARGE` ou `INVALID_ID` : vérifications locales, rien n'a été envoyé                      |
| HTML de proxy ou corps non JSON qui s'échappe                   | `UNKNOWN_ERROR` avec le vrai statut et le message `HTTP <status>` (`Invalid JSON in HTTP <status> response` sur un 2xx) |

`str(e)` vaut `'<METHOD> <path>: HTTP <status> <CODE>: <message>'`, sans `HTTP <status>` lorsque le statut est `0` et sans `: <message>` lorsqu'il est vide. `e.message` vaut `''` lorsque le serveur n'a envoyé qu'un code.

## Changements de comportement

* Les modificateurs optionnels sont uniquement nommés : `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=)`, les filtres de `apps.network.analytics(...)`, `databases.update(id, name=, ram=)` et `databases.create(name, type=, version=, memory=)`. Le `path` optionnel de `apps.files.list` reste positionnel.
* Un corps 2xx `{"status": "error"}` lève une erreur. Les refus de démarrage/arrêt des applications et des bases de données donnent 409 avec seulement un code (`CONTAINER_ALREADY_STARTED`, `ACTION_FAILED`, ...). Un 202 `SNAPSHOT_PROCESSING` renvoie `{'pending': True}` au lieu de lever une erreur : interrogez `list`, n'appelez jamais `create` à nouveau.
* Les valeurs de requête optionnelles non définies (et `''`) sont omises au lieu d'être envoyées.
* Les résultats de type chaîne ne sont jamais `None` : `reset_credentials(id, 'certificate')` et un webhook supprimé renvoient `''`.
* `apps.files.write` envoie une `str` en texte et des `bytes` encodés en base64 ; un contenu vide crée un fichier vide ; un contenu de plus de 1 MiB est envoyé sans timeout. `apps.files.read` demande toujours du base64 et renvoie les `bytes` décodés.
* `apps.files.list` sur un répertoire inexistant lève 404 `FILE_NOT_FOUND`.
* 503 `DATABASE_UNAVAILABLE` n'est réessayé que sur `GET`, car il peut survenir après l'application d'une mutation ; réessayer une mutation idempotente revient à l'appelant.
* Le flux temps réel produit des événements `{'event', 'data', 'id', ...}`, se rouvre au plus 3 fois d'affilée à raison d'une ouverture toutes les 5,5 s, et lève une erreur lorsqu'une ouverture échoue.

## Asynchrone

La v4 était uniquement asynchrone. Dans la v5, `SquareCloud` est synchrone et `AsyncSquareCloud` est la façade `await` : les mêmes groupes et méthodes, chaque appel étant exécuté dans `asyncio.to_thread`, de sorte que la boucle d'événements n'est jamais bloquée. Le flux temps réel devient `async for` (un thread de lecture alimente la boucle) ; fermez-le avec `async with` ou `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'])
```
