Metadata-Version: 2.4
Name: dirigex-sdk
Version: 0.1.0
Summary: Python SDK for Dirigex self-service APIs
Author: Dirigex
License-Expression: MIT
Project-URL: Homepage, https://github.com/DirigexAI/Dirigex
Project-URL: Repository, https://github.com/DirigexAI/Dirigex
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1,>=0.27
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == "dev"
Requires-Dist: mypy<2,>=1.10; extra == "dev"
Dynamic: license-file

# Dirigex Python SDK (v0.1.0)

Python SDK for Dirigex self-service endpoints:

- `POST /self/register/bootstrap`
- `GET /self/me`
- `POST /self/session/refresh-context`
- `GET /self/tenant/catalog/agents`
- `POST /self/tenant/agents/{agentId}/invoke`
- `POST /self/tenant/agents/register`
- `GET /self/tenant/usage`
- `GET /self/tenant/audit-events`
- `POST /self/tenant/export`
- `POST /self/tenant/webhooks`
- `GET /self/tenant/webhooks`
- `POST /self/tenant/webhooks/{subscriptionId}/revoke`

## Install

```bash
pip install dirigex-sdk
```

For reproducible production installs:

```bash
pip install "dirigex-sdk==0.1.0"
```

During local repository development, use an editable workspace install:

```bash
pip install -e packages/dirigex-sdk-python
```

## Sync quickstart

```python
from dirigex_sdk import create_dirigex_client

client = create_dirigex_client(
    base_url="https://api.dirigex.ai",
    get_token=lambda: "<bearer-jwt>",
)

bootstrap = client.self.bootstrap()
me = client.self.me()
refreshed = client.self.refresh_context()
```

## Async quickstart

```python
from dirigex_sdk import create_async_dirigex_client

async def run() -> None:
    async with create_async_dirigex_client(
        base_url="https://api.dirigex.ai",
        get_token=lambda: "<bearer-jwt>",
    ) as client:
        bootstrap = await client.self.bootstrap()
        me = await client.self.me()
        refreshed = await client.self.refresh_context()
```

## Authentication integration

`get_token` is caller-managed and invoked per request.

- Sync client: `get_token: Callable[[], str]`
- Async client: `get_token: Callable[[], str | Awaitable[str]]`

Common pitfall: stale cached tokens. If you see `invalid_token`, refresh in your token provider.

If `get_token` returns an empty or whitespace-only token, the SDK throws
`auth_token_unavailable` before sending any network request.

## Error handling

```python
from dirigex_sdk import (
    DirigexError,
    RateLimitedError,
    InvalidTokenError,
    TenantSuspendedError,
    ConsumerEmailDomainBlockedError,
)

try:
    client.self.bootstrap()
except RateLimitedError as error:
    # error.retry_after_seconds may be populated from Retry-After
    pass
except InvalidTokenError:
    pass
except TenantSuspendedError:
    pass
except ConsumerEmailDomainBlockedError:
    pass
except DirigexError as error:
    print(error.code, error.raw_server_code, error.status)
```

### Server-emitted codes (14)

- `authentication_required`
- `invalid_token`
- `self_service_auth_unavailable`
- `email_required`
- `invalid_email_domain`
- `tenant_not_found`
- `tenant_membership_not_found`
- `tenant_suspended`
- `user_disabled`
- `user_not_active`
- `email_not_verified`
- `consumer_email_domain_blocked`
- `tenant_create_race_unresolved`
- `rate_limited`

### SDK-emitted codes

- `network_error`
- `non_json_error`
- `malformed_response`
- `unknown_server_error`

`raw_server_code` is always preserved for server-emitted errors. For SDK-emitted errors it is an empty string.

## Retry behavior

Default retry config:

```python
{
    "on_rate_limited": True,
    "on_service_unavailable": False,
    "max_attempts": 3,
    "respect_retry_after": True,
    "max_retry_after_seconds": 60,  # optional; default 60; cap on Retry-After waits
}
```

- `429 rate_limited`: retries by default.
- `503 self_service_auth_unavailable`: retry is opt-in.
- `Retry-After` supports both numeric seconds and HTTP-date formats.
- `Retry-After` waits are capped via `max_retry_after_seconds` (default 60) so a
  misconfigured or hostile server cannot stall the client indefinitely. The cap
  only affects internal retry timing — `error.retry_after_seconds` exposed to
  callers preserves the original uncapped value from the server header for triage.

## Transport security

`base_url` must use `https://` for any non-localhost host. The SDK rejects
plaintext-HTTP URLs at construction time (`ValueError`) to prevent bearer
tokens from being transmitted unencrypted. `http://localhost`,
`http://127.0.0.1`, and `http://[::1]` are accepted for local development.

## Type-shape contract

Response objects preserve wire-format field names (camelCase) to match the API contract:

- Bootstrap: `tenantId`, `role`, `onboardingState`
- Me/refresh: `userSub`, `email`, `tenantId`, `roles`, `capabilities`

## License

MIT — see [LICENSE](./LICENSE).
