# andco-sdk · Python

> Cliente confidencial, OAuth y API de recursos para backends de Python.


```bash tab="python:uv"
uv add andco-sdk
```

```bash tab="python:pip"
pip install andco-sdk
```

```bash tab="python:poetry"
poetry add andco-sdk
```

Requiere Python 3.11 o superior. Es un SDK de backend: cliente confidencial, sin navegador y sin presentaciones.

Sigue el mismo vocabulario que el SDK de JavaScript pero es idiomático en Python: **lanza excepciones** en vez de devolver resultados, usa `snake_case` y es asíncrono sobre `httpx`.

## AndcoClient

```python
AndcoClient(
    *,
    client_id: str,
    client_secret: str | None = None,
    auth_endpoint: str = "https://auth.andco.cl",
    api_endpoint: str = "https://api.andco.cl",
    api_version: str = "_",
    resource: str = "https://api.andco.cl",
    transport: TransportMode = "auto",
    http_transport: httpx.AsyncBaseTransport | None = None,
)
```

Un valor: configuración más capacidad, construido de forma síncrona y sin sesión. Una instancia de módulo es segura entre peticiones concurrentes precisamente porque no existe el campo donde el token de una persona podría filtrarse a la respuesta de otra.

`http_transport` existe para los tests: permite inyectar un transporte de `httpx` sin tocar la red.

### with_session

```python
with_session(credentials: AndcoSession | str | Credentials) -> BoundAndcoClient
```

Acepta una sesión, un token suelto o cualquier cosa que resuelva uno. El token suelto está aceptado porque es de lejos la forma más común en un servidor, y exigir un envoltorio ponía las mismas tres líneas al inicio de cada manejador.

`BoundAndcoClient` tiene `rest` y la configuración de su dueño.

## AndcoOAuth

Disponible como `client.oauth`.

```python
async create_authorization_request(
    *,
    redirect_to: str,
    scopes: list[str] | None = None,
    resource: str | None = None,
    org_id: str | None = None,
    authorization_details: list[dict] | None = None,
    login_hint: str | None = None,
    state: str | None = None,
) -> AndcoAuthorizationRequest

async exchange_callback(*, callback_url: str, request: AndcoAuthorizationRequest) -> AndcoSession
async refresh(refresh_token: str) -> AndcoSession
def endpoint(name: str) -> str
property is_confidential: bool
```

La decisión entre petición directa y PAR ocurre dentro de `create_authorization_request`, así que quien llama nunca la toma. Se usa PAR cuando la petición lleva `login_hint` o `authorization_details`, o cuando la URL quedaría demasiado larga.

`exchange_callback` valida el callback contra la transacción —origen, `state`, antigüedad— antes de intercambiar nada.

## AndcoSession

```python
@dataclass(frozen=True, slots=True)
class AndcoSession:
    access_token: str
    refresh_token: str | None
    expires_at: float          # segundos desde la época Unix
    scopes: tuple[str, ...] = ()
    token_type: str = "bearer"
    user: AndcoUser | None = None

    @property
    def is_expired(self) -> bool: ...
```

`is_expired` incluye un margen, de modo que un token a punto de vencer se renueva antes de salir a la red.

## AndcoAuthorizationRequest

```python
@dataclass(frozen=True, slots=True)
class AndcoAuthorizationRequest:
    authorization_url: str
    code_verifier: str
    state: str
    redirect_to: str
```

Es lo que hay que guardar entre la redirección y la vuelta. El `code_verifier` no puede viajar en la URL y no se puede reconstruir.

## AndcoRest

```python
async get(path: str, *, model: type[Model] | None = None, **params) -> Any
async post(path: str, body: Any = None, *, model: type[Model] | None = None) -> Any
async request(...) -> Any
```

`model` valida y tipa la respuesta cuando quieres un objeto en vez de un diccionario.

## Errores

```python
class AndcoError(Exception):
    code: str
    status_code: int | None
    details: dict | None

class AndcoAuthorizationError(AndcoError): ...
```

Excepciones y no resultados: es lo que se espera de una biblioteca de Python, y `try` / `except` ya es el mecanismo del lenguaje para esto.

## Webhooks

```python
from andco_sdk import (
    ANDCO_WEBHOOK_EVENT_ID_HEADER,
    ANDCO_WEBHOOK_SIGNATURE_HEADER,
    ANDCO_WEBHOOK_TIMESTAMP_HEADER,
    verify_webhook,
)

trusted = verify_webhook(
    body=raw,
    signature=headers.get(ANDCO_WEBHOOK_SIGNATURE_HEADER),
    event_id=headers.get(ANDCO_WEBHOOK_EVENT_ID_HEADER),
    timestamp=headers.get(ANDCO_WEBHOOK_TIMESTAMP_HEADER),
    secret=settings.ANDCO_WEBHOOK_SECRET,
)
```

La comparación es de tiempo constante y una entrega fuera de la ventana de tolerancia se rechaza aunque su firma sea válida. `body` tiene que ser los bytes crudos.

## Modelos

```python
from andco_sdk import (
    ANDCO_RESOURCE,
    AndcoCompanyAddress,
    AndcoCompanyIdentity,
    AndcoGrantAuthorization,
)
```

Tipos de los recursos de empresa y de las contribuciones de autorización, para que no los redeclares.

## Qué no hace

No presenta autorizaciones, no abre ventanas, no guarda sesión y no tiene almacenamiento. Todo eso es del lado del navegador, y un backend de Python no está ahí.
