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

# Migración a v5

> Qué cambió entre squarecloud-api v4 y v5: un cliente síncrono con una fachada await, dicts planos en lugar de dataclasses, métodos agrupados por recurso y una única clase de excepción. Una tabla método por método.

La v5 es una reescritura. Ahora el SDK es síncrono por defecto (con una fachada `await`), no tiene dependencias, agrupa los métodos por recurso, devuelve dicts planos (`TypedDict`) y lanza un único tipo de excepción. Cubre las 67 operaciones de la API de Square Cloud.

## De un vistazo

| v4                                                                                                     | v5                                                                                                                                                                 |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `await client.x(...)`, solo asíncrono                                                                  | `client.group.x(...)`, o `await async_client.group.x(...)`                                                                                                         |
| Dataclasses congeladas (`status.ram`)                                                                  | `TypedDict`, un dict plano (`status['ram']`); los campos desconocidos se conservan en lugar de lanzar `TypeError`                                                  |
| Objetos `Application` (`app.start()`, `app.cache`)                                                     | Datos planos más ids: `client.apps.start(app_id)`. No hay caché                                                                                                    |
| \~25 clases de excepción (`NotFoundError`, `TooManyRequests`, `FewMemory`, ...)                        | `SquareCloudAPIError` con `.status`, `.code`, `.message`, `.method`, `.path` (`'/v2/...'`), `.cause`                                                               |
| `squarecloud.File(path)`                                                                               | Pasa directamente una ruta, `bytes` o un objeto de archivo binario                                                                                                 |
| Argumentos opcionales por posición                                                                     | Los modificadores opcionales son solo por palabra clave (`status(id, raw=True)`, `commit(id, file, path=...)`, `databases.create(name, type=, version=, memory=)`) |
| Listeners de peticiones (`@client.on_request`), listeners de captura, `avoid_listener`, `update_cache` | Eliminados. Usa el [logger](/es/sdks/py/client#logging) `squarecloud` (DEBUG) o un [`transport=`](/es/sdks/py/client#transporte-personalizado) personalizado       |
| Dependencias `aiohttp` y `typing-extensions`                                                           | Ninguna                                                                                                                                                            |
| Python `>=3.13,<3.15`                                                                                  | Python `>=3.11`, sin límite superior                                                                                                                               |

## Construcción y opciones

| 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=...)`, o `AsyncSquareCloud(...)` con los mismos argumentos                     |
| `log_level=` y un handler con colores añadido al importar                                 | `logging.getLogger('squarecloud')` con un `NullHandler`; configúralo tú mismo                                                                                                      |
| Valores por defecto de `aiohttp` (5 min por petición; 90 s entre lecturas de tiempo real) | `timeout` en segundos por operación de socket, con un mínimo de 120 s para las llamadas que el servidor mantiene abiertas y las de IA; `timeout <= 0` desactiva todos los timeouts |
| Sin reintentos                                                                            | `max_retries` (2) solo para los fallos seguros; un 429 nunca se reintenta                                                                                                          |
| `User-Agent` fijo en el código                                                            | `user_agent=` reemplaza el encabezado (por defecto `squarecloud-sdk-py/<version>`)                                                                                                 |
| Una nueva sesión por petición                                                             | `close()` o `with` / `async with` cierra las conexiones keep-alive del pool                                                                                                        |

## Método por método

| antiguo (v4 `Client`)                                                                                | nuevo (v5 `SquareCloud`)                                                             | notas                                                                                                                                                                                                                                                                                                                               |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user()`                                                                                             | `account.me()['user']`                                                               | `me()` también devuelve `applications` y `databases`                                                                                                                                                                                                                                                                                |
| `all_apps()`                                                                                         | `account.me()['applications']`                                                       |                                                                                                                                                                                                                                                                                                                                     |
| `app(app_id)`                                                                                        | `apps.get(app_id)`                                                                   | Ahora `GET /apps/{id}`: funciona con ids de workspace `'<appId>-<workspaceId>'`                                                                                                                                                                                                                                                     |
| `user_snapshots(scope)`                                                                              | `account.snapshots(*, scope=None)`                                                   | `scope` es solo por palabra clave. Los elementos incluyen el `version_id` y la `url` firmada de la API                                                                                                                                                                                                                              |
| `service_status()`                                                                                   | `service.status()`                                                                   |                                                                                                                                                                                                                                                                                                                                     |
| `upload_app(File(path))`                                                                             | `apps.create(path)`                                                                  | Se transmite desde el disco; devuelve `AppCreated` (`domain` es el host completo de un sitio web; no hay `subdomain`)                                                                                                                                                                                                               |
| `delete_app(id)`                                                                                     | `apps.delete(id)`                                                                    | Devuelve `None`                                                                                                                                                                                                                                                                                                                     |
| `all_apps_status()`                                                                                  | `apps.status_all(*, workspace_id=None)`                                              | Las aplicaciones detenidas ya no se omiten                                                                                                                                                                                                                                                                                          |
| `app_status(id)`                                                                                     | `apps.status(id, *, raw=False)`                                                      | `raw=True` (solo por palabra clave) devuelve números                                                                                                                                                                                                                                                                                |
| `start_app` / `stop_app` / `restart_app`                                                             | `apps.start` / `apps.stop` / `apps.restart`                                          | Devuelven `None`                                                                                                                                                                                                                                                                                                                    |
| `get_logs(id)`                                                                                       | `apps.logs(id)`                                                                      | Devuelve el `str`                                                                                                                                                                                                                                                                                                                   |
| `app_metrics(id)`                                                                                    | `apps.metrics(id)`                                                                   |                                                                                                                                                                                                                                                                                                                                     |
| `realtime(id)` (generador asíncrono de cada línea `data:`, decodificada como JSON cuando es posible) | `apps.realtime(id)`                                                                  | Iterador de `{'event', 'data', 'id'}` con el texto `data` sin procesar, más `stream`/`line` en los logs y el `status` combinado en los eventos de estado. Se reconecta por sí solo (consulta Cambios de comportamiento); el stream no tiene timeout de lectura (la v4 se rendía tras 90 s sin datos), así que detenlo con `close()` |
| `all_domains()`                                                                                      | `apps.domains()`                                                                     |                                                                                                                                                                                                                                                                                                                                     |
| `load_balancers()`                                                                                   | `apps.load_balancers()`                                                              |                                                                                                                                                                                                                                                                                                                                     |
| `commit(id, File(path))`                                                                             | `apps.commit(id, file, *, path=None, filename=None)`                                 | `path`, solo por palabra clave, es el directorio de destino; `filename` da nombre a un archivo que no es zip (los `bytes` usan `commit.zip` por defecto)                                                                                                                                                                            |
| `github_integration(id, access_token)`                                                               | `apps.deploys.set_webhook(id, access_token)`                                         |                                                                                                                                                                                                                                                                                                                                     |
| `last_deploys(id)`                                                                                   | `apps.deploys.list(id)`                                                              | Los eventos incluyen `source`, `branch`, `files`; un deploy fallido termina con `state='error'` más `code` y `message`                                                                                                                                                                                                              |
| `current_app_integration(id)`                                                                        | `apps.deploys.current(id)`                                                           | Devuelve `{'app'?, 'webhook'?}` en lugar del string del 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)`                                                     | Las entradas son el `FileEntry` de la API (sin `path`/`app_id` calculados). Un directorio inexistente lanza 404 `FILE_NOT_FOUND` en lugar de devolver `[]`; uno bloqueado da 403 `BLOCKED_PATH`                                                                                                                                     |
| `read_app_file(id, path)` → `BytesIO`                                                                | `apps.files.read(id, path)` → `bytes`                                                | Se obtiene codificado en base64 (`?encoding=base64`) y se decodifica; más de 10 MB da 413 `FILE_TOO_LARGE`                                                                                                                                                                                                                          |
| `create_app_file(id, File(...), path)`                                                               | `apps.files.write(id, path, content)`                                                | Un `str` se envía como texto, los `bytes` codificados en base64 (seguro para binarios); la ruta se envía tal cual (sin `/` adicional); un contenido vacío (`''` o `b''`) crea un archivo vacío                                                                                                                                      |
| `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}` en un 202 en lugar de lanzar un error; después consulta `list`, nunca vuelvas a crearlo                                                                                                                                                                                                                         |
| `all_app_snapshots(id)`                                                                              | `apps.snapshots.list(id)`                                                            | Los elementos incluyen el `version_id` y la `url` de descarga firmada de la API, tal como se envían                                                                                                                                                                                                                                 |
| `restore_snapshot('app', id, snapshot_id, version_id)`                                               | `apps.snapshots.restore(id, name, version_id)`                                       | `name` y `version_id` de un elemento de `list`                                                                                                                                                                                                                                                                                      |
| `restore_snapshot('database', id, ...)`                                                              | `databases.snapshots.restore(id, name, version_id)`                                  | `name` y `version_id` de un elemento de `list`                                                                                                                                                                                                                                                                                      |
| `Snapshot.download(path)`                                                                            | `client.download_snapshot(url, dest)`                                                | Transmite el propio zip (la v4 lo envolvía en otro zip)                                                                                                                                                                                                                                                                             |
| `domain_analytics(id, start=, end=, ...)`                                                            | `apps.network.analytics(id, start, end, *, country=, ...)`                           | La API exige `start`/`end`; los filtros son solo por palabra clave                                                                                                                                                                                                                                                                  |
| `network_errors(id, start, end, include_4xx)` / `network_logs` / `network_performance`               | `apps.network.errors(id, start, end, *, include_4xx=False)` / `logs` / `performance` | `analytics`, `errors` y `performance` devuelven `None` para una ventana sin datos                                                                                                                                                                                                                                                   |
| `dns_records(id)`                                                                                    | `apps.network.dns(id)`                                                               |                                                                                                                                                                                                                                                                                                                                     |
| `set_custom_domain(id, custom_domain)`                                                               | `apps.network.set_domain(id, domain)`                                                | La v4 nunca enviaba el cuerpo                                                                                                                                                                                                                                                                                                       |
| `purge_cache(id)`                                                                                    | `apps.network.purge_cache(id)`                                                       |                                                                                                                                                                                                                                                                                                                                     |
| `link_github_app(id, repository_name, repository_branch)`                                            | `apps.deploys.link_github_app(id, repository, branch)`                               | Ahora funciona con una clave de API (scope `apps:deploy`); devuelve el `LinkedRepository` (`{id, full_name, branch}`) en lugar del dict sin procesar; 403 `GITHUB_NOT_CONNECTED` sin una instalación de la GitHub App                                                                                                               |
| `unlink_github_app(id)`                                                                              | `apps.deploys.unlink_github_app(id)`                                                 | Devuelve `None`; 400 `GIT_NOT_CONFIGURED` cuando no hay nada vinculado                                                                                                                                                                                                                                                              |
| `create_database(name, memory, type, version=None)`                                                  | `databases.create(name, type=, version=, memory=)`                                   | Solo por palabra clave; `version` es obligatorio (sirve una versión mayor como `'8'`)                                                                                                                                                                                                                                               |
| `get_database_info(id)`                                                                              | `databases.get(id)`                                                                  |                                                                                                                                                                                                                                                                                                                                     |
| `edit_database(id, name, memory)`                                                                    | `databases.update(id, name=, ram=)`                                                  | La v4 enviaba `memory`, que la API ignoraba                                                                                                                                                                                                                                                                                         |
| `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(...)` da el PEM                                                                                                                                                                                                                                                                                                   |
| `reset_database_password(id)`                                                                        | `databases.reset_credentials(id, 'password')`                                        | Devuelve la nueva contraseña                                                                                                                                                                                                                                                                                                        |
| `reset_database_certificate(id)`                                                                     | `databases.reset_credentials(id, 'certificate')`                                     | Devuelve `''`                                                                                                                                                                                                                                                                                                                       |
| `all_database_snapshots(id)` / `database_snapshot(id)`                                               | `databases.snapshots.list(id)` / `create(id)`                                        | Los elementos incluyen `version_id` y `url`, igual que en las aplicaciones                                                                                                                                                                                                                                                          |
| `create_workspace(name)`                                                                             | `workspaces.create(name)`                                                            | Devuelve `{id, name}` en una sola petición                                                                                                                                                                                                                                                                                          |
| `all_workspaces()` / `get_workspace(id)`                                                             | `workspaces.list()` / `workspaces.get(id)`                                           | La v4 reescribía el `id` de cada aplicación con el compuesto `'<appId>-<workspaceId>'`; la v5 devuelve el id sin procesar de la API, así que constrúyelo tú mismo: `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)`                                                                   | Nuevo; `request` es el cuerpo al estilo de OpenAI (`{'messages': [...], 'model': ...}`)                                                                                                                                                                                                                                             |
| `client.api_key`                                                                                     | Eliminado                                                                            | Guarda tú mismo una referencia a la clave                                                                                                                                                                                                                                                                                           |
| `on_request(endpoint)`, `Application.capture(endpoint)`                                              | Eliminados                                                                           | Consulta la fila de los listeners en [De un vistazo](#de-un-vistazo)                                                                                                                                                                                                                                                                |
| `squarecloud.utils.ConfigFile`                                                                       | Eliminado                                                                            | Escribe tú mismo el archivo `squarecloud.app` (líneas `KEY=value`)                                                                                                                                                                                                                                                                  |

Los métodos de `Application` se corresponden con las mismas llamadas usando el id: `app.logs()` → `client.apps.logs(app.id)`, `app.files_list(path)` → `client.apps.files.list(app.id, path)`, etc.

## Tipos

Las respuestas son `TypedDict` de `squarecloud.types`, con los mismos nombres que en los SDK de JS y 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`. Reemplazan las dataclasses `data/*` de la v4 (`UserData`, `StatusData`, `AppData`, ...). `squarecloud.Response` es ahora el protocolo de respuesta del transporte (consulta [Transporte personalizado](/es/sdks/py/client#transporte-personalizado)); el `Response` de la v4 que devolvían las mutaciones ya no existe, y ahora devuelven `None`.

## Errores

| v4                                                                 | v5                                                                                                                   |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `AuthenticationFailure`                                            | `e.status == 401` (`ACCESS_DENIED`, también para una clave caducada)                                                 |
| `NotFoundError`, `ApplicationNotFound`                             | `e.status == 404` (`APP_NOT_FOUND`, `WORKSPACE_NOT_FOUND`, ...)                                                      |
| `BadRequestError` y la familia `InvalidConfig`                     | `e.status == 400`, comprueba `e.code`                                                                                |
| `TooManyRequests`                                                  | `e.status == 429` (`RATE_LIMITED`, `KEEP_CALM`)                                                                      |
| `FewMemory` (nunca se lanzaba: la API envía `INSUFFICIENT_MEMORY`) | `e.code == 'INSUFFICIENT_MEMORY'`                                                                                    |
| `InvalidDomain` (`REGEX_VALIDATION`, ya no se envía)               | `e.code == 'INVALID_DOMAIN'`                                                                                         |
| `RequestError` para 403/503                                        | `e.code` en `MISSING_SCOPE`, `RESOURCE_NOT_ALLOWED`, `BLOCKED_PATH`, `UPLOAD_BUSY`, ...                              |
| Excepciones de `aiohttp` que se filtraban                          | `e.status == 0`, `e.code` en `NETWORK_ERROR`, `TIMEOUT` (excepción original en `e.cause`)                            |
| —                                                                  | `e.status == 0` con `FILE_TOO_LARGE` o `INVALID_ID`: comprobaciones locales, no se envió nada                        |
| HTML de un proxy o un cuerpo no JSON que se filtraba               | `UNKNOWN_ERROR` con el estado real y el mensaje `HTTP <status>` (`Invalid JSON in HTTP <status> response` en un 2xx) |

`str(e)` es `'<METHOD> <path>: HTTP <status> <CODE>: <message>'`, sin `HTTP <status>` cuando el estado es `0` y sin `: <message>` cuando está vacío. `e.message` es `''` cuando el servidor solo envió un código.

## Cambios de comportamiento

* Los modificadores opcionales son solo por palabra clave: `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=)`, los filtros de `apps.network.analytics(...)`, `databases.update(id, name=, ram=)` y `databases.create(name, type=, version=, memory=)`. El `path` opcional de `apps.files.list` sigue siendo posicional.
* Un cuerpo 2xx `{"status": "error"}` lanza un error. Los rechazos de inicio/detención de aplicaciones y bases de datos son un 409 con solo un código (`CONTAINER_ALREADY_STARTED`, `ACTION_FAILED`, ...). Un 202 `SNAPSHOT_PROCESSING` devuelve `{'pending': True}` en lugar de lanzar un error: consulta `list`, nunca vuelvas a llamar a `create`.
* Los valores de query opcionales no definidos (y `''`) se omiten en lugar de enviarse.
* Los resultados de tipo string nunca son `None`: `reset_credentials(id, 'certificate')` y un webhook eliminado devuelven `''`.
* `apps.files.write` envía un `str` como texto y los `bytes` codificados en base64; un contenido vacío crea un archivo vacío; un contenido de más de 1 MiB se envía sin timeout. `apps.files.read` siempre pide base64 y devuelve los `bytes` decodificados.
* `apps.files.list` de un directorio inexistente lanza 404 `FILE_NOT_FOUND`.
* Un 503 `DATABASE_UNAVAILABLE` solo se reintenta en `GET`, porque puede producirse después de que se haya aplicado una mutación; reintentar una mutación idempotente queda en manos de quien llama.
* El stream de tiempo real produce eventos `{'event', 'data', 'id', ...}`, se reabre como máximo 3 veces seguidas a razón de una apertura cada 5,5 s, y lanza un error cuando falla una apertura.

## Asíncrono

La v4 era solo asíncrona. En la v5, `SquareCloud` es síncrono y `AsyncSquareCloud` es la fachada `await`: los mismos grupos y métodos, con cada llamada ejecutada en `asyncio.to_thread`, así que el event loop nunca se bloquea. El stream de tiempo real pasa a ser `async for` (un hilo lector alimenta el bucle); ciérralo con `async with` o `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'])
```
