Metadata-Version: 2.4
Name: parloa
Version: 1.0.0
Summary: 
License: Apache-2.0
License-File: LICENSE
License-File: LICENSE-HEADER
Requires-Python: >=3.10,<4.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: OS Independent
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Provides-Extra: aiohttp
Requires-Dist: aiohttp (>=3.14.1,<4) ; (python_version >= "3.10") and (extra == "aiohttp")
Requires-Dist: certifi (>=2026.10.19)
Requires-Dist: httpx (>=0.21.2)
Requires-Dist: httpx-aiohttp (>=0.1.8,<0.2.0) ; (python_version >= "3.10") and (extra == "aiohttp")
Requires-Dist: pydantic (>=1.9.2)
Requires-Dist: pydantic-core (>=2.18.2,<3.0.0)
Requires-Dist: typing_extensions (>=4.0.0)
Project-URL: Repository, https://github.com/parloa/parloa-sdk-python
Description-Content-Type: text/markdown

# Parloa Python SDK

Official Python client for the Parloa API.

## Installation

```bash
pip install parloa
```

## Usage

Authenticate with `Parloa.token_provider()`. It caches OAuth tokens and refreshes them before expiry. Pass `provider.create_sync_fetcher()` to `Parloa` and `provider.create_async_fetcher()` to `AsyncParloa`. Any `RefreshableTokenProvider` or `AsyncRefreshableTokenProvider` is retried once after a 401 by calling `refresh()`. `base_url` is required. Pass `EnvironmentBaseUrl` to derive the gateway from a Parloa environment and resolve tenant gateways, or `StaticBaseUrl` to pin every request to one URL (see [Automatic gateway resolution](#automatic-gateway-resolution)).

```python
from parloa import Parloa, ParloaEnvironment
from parloa.base_url import EnvironmentBaseUrl

CLIENT_ID = "client_01HZX9YQK0V4N4G9Z8B7Q1T2C3"
CLIENT_SECRET = "client_secret"
BLUEPRINT_ID = "9e107d9d-372b-4144-a03c-1b1a5f9b4b18"

base_url = EnvironmentBaseUrl(environment=ParloaEnvironment.PRODUCTION_EU)
provider = Parloa.token_provider(
    client_id=CLIENT_ID,
    client_secret=CLIENT_SECRET,
    base_url=base_url,
)

client = Parloa(base_url=base_url, token=provider.create_sync_fetcher())
channels = client.channels.list()
print(channels.data[0].name)

skills = client.skills.list(blueprint_id=BLUEPRINT_ID)
print(skills.data[0].name)
```

Async usage follows the same pattern with `AsyncParloa`:

```python
import asyncio

from parloa import AsyncParloa, ParloaEnvironment
from parloa.base_url import EnvironmentBaseUrl

async def main() -> None:
    base_url = EnvironmentBaseUrl(environment=ParloaEnvironment.PRODUCTION_EU)
    provider = AsyncParloa.token_provider(
        client_id="client_01HZX9YQK0V4N4G9Z8B7Q1T2C3",
        client_secret="client_secret",
        base_url=base_url,
    )
    client = AsyncParloa(base_url=base_url, token=provider.create_async_fetcher())
    channels = await client.channels.list()
    print(channels.data[0].name)

asyncio.run(main())
```

### Custom request headers

Pass `headers` to send extra headers on every request, for example a `User-Agent` that identifies your tool. Pass the same headers to `token_provider()` so token requests carry them too. The SDK's own `X-Fern-*` headers are still sent.

```python
headers = {"User-Agent": "example-tool/1.2.3"}
provider = Parloa.token_provider(
    client_id=CLIENT_ID,
    client_secret=CLIENT_SECRET,
    base_url=EnvironmentBaseUrl(environment=ParloaEnvironment.PRODUCTION_EU),
    headers=headers,
)
client = Parloa(
    base_url=EnvironmentBaseUrl(environment=ParloaEnvironment.PRODUCTION_EU),
    token=provider.create_sync_fetcher(),
    headers=headers,
)
```

## Automatic gateway resolution

`base_url` is required. It is one of two objects, distinguished by `type`:

- `EnvironmentBaseUrl` (`type="environment"`) — the SDK derives the control-plane gateway from `environment` (default `ParloaEnvironment.PRODUCTION_US`) and resolves the correct API gateway for each API group before sending a request.
- `StaticBaseUrl` (`type="static"`) — every sub-client is pinned to `base_url` by default. Automatic resolution is off. Use this only if you were explicitly instructed to do so for the SDK integration.

`StaticBaseUrl` also takes a `resolve_base_url` flag (default `False`). Set it to `True` to resolve tenant gateways through the tenant URLs API — using `base_url` as the control-plane host for the resolution request — for a host that isn't one of the published `ParloaEnvironment` members, for example a testing gateway. Use this only for testing against your local testing mocks, or if explicitly instructed by a Parloa representative.

```python
from parloa import Parloa, ParloaEnvironment
from parloa.base_url import EnvironmentBaseUrl, StaticBaseUrl

client = Parloa(
    base_url=EnvironmentBaseUrl(environment=ParloaEnvironment.PRODUCTION_EU),
    token=provider.create_sync_fetcher(),
)
channels = client.channels.list()

client = Parloa(
    base_url=StaticBaseUrl(base_url="https://your.special.host"),
    token=provider.create_sync_fetcher(),
)

# A host outside the published environments, with resolution turned back on
client = Parloa(
    base_url=StaticBaseUrl(base_url="https://your.special.host", resolve_base_url=True),
    token=provider.create_sync_fetcher(),
)
```

### What happens on each request

With `EnvironmentBaseUrl`, each request follows this flow:

1. You access a sub-client, for example `client.channels`.
2. Before the HTTP request is sent, an httpx event hook looks up your URL map — from an in-memory cache when available, otherwise from the tenant URLs API (`GET /v1/urls`).
3. The hook looks up the tag for that sub-client (e.g. `channels`) and rewrites the request host to the tenant URL.
4. The actual API call goes to the resolved gateway. A resolved host that is not `parloa.com`, `parloa.us`, or a subdomain of either is refused and the request is not sent.

Tenant URLs are cached for one hour, so later calls to the same process reuse the map without another `/v1/urls` round trip.

```plantuml
@startuml
actor User
participant "Parloa" as Client
participant "__getattribute__" as GetAttr
participant "Tenant URLs\n(/v1/urls)" as TenantUrls
participant "Channels API" as Channels

User -> Client : channels.list()
Client -> GetAttr : access client.channels
GetAttr -> Client : build ChannelsClient\n(with URL resolver hook)
Client -> TenantUrls : GET /v1/urls\n(cached after first call)
TenantUrls --> Client : {"channels": "https://runtime.example.com", ...}
Client -> Channels : GET /v1/channels\n(host rewritten)
Channels --> Client : response
Client --> User : channels
@enduml
```

The `tenant_urls` sub-client is excluded from this rewrite — it must always hit the default API gateway to fetch the map.

