Metadata-Version: 2.4
Name: wlt-platform
Version: 0.1.12
Summary: Official Python SDK for the WLT Console API
Project-URL: Homepage, https://github.com/wlt/wlt-python
Project-URL: Documentation, https://docs.wlt.com/sdk/python
Project-URL: Repository, https://github.com/wlt/wlt-python
Author-email: WLT <sdk@wlt.com>
License-Expression: Apache-2.0
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: click>=8.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0
Description-Content-Type: text/markdown

# WLT Python SDK

Official Python client library for the WLT Console API.

- **Python**: 3.8+
- **Dependencies**: [httpx](https://www.python-httpx.org/) >= 0.25, [pydantic](https://docs.pydantic.dev/) v2

## Installation

```bash
pip install wlt-platform
```

To pin a specific version:

```bash
pip install wlt-platform==0.1.12
```

## Quick Start

```python
from wlt import WltClient

# Initialize the client with your API Key (obtain from Console > Secret Management)
# Authentication happens EAGERLY at construction time for the sync WltClient:
#   1. WltClient(api_key=...) immediately calls loginByApiKey to exchange the
#      api_key for a short-lived Bearer token. An invalid api_key raises
#      AuthenticationError right here -- before any business call is made.
#   2. The token is cached on the client and sent as Authorization: Bearer ...
#      with every subsequent request.
#   3. If a later response is an auth failure (HTTP 401/403 or business code in
#      {2000, 2002, 2007, 3001}), the SDK re-runs loginByApiKey, swaps the
#      cached token, and retries the failed request once.
# (AsyncWltClient defers the first loginByApiKey to the first awaited call.)
client = WltClient(api_key="sk-your-api-key")

# Example: list secrets
result = client.secrets.list()
print(result.data)

# Always close the client when done
client.close()
```

### Context Manager

```python
from wlt import WltClient

with WltClient(api_key="sk-your-api-key") as client:
    result = client.secrets.list()
    print(result.data)
# client is automatically closed
```

### Async Usage

```python
import asyncio
from wlt import AsyncWltClient

async def main():
    async with AsyncWltClient(api_key="sk-your-api-key") as client:
        result = await client.secrets.list()
        print(result.data)

asyncio.run(main())
```

> All synchronous examples below have identical async equivalents -- just use
> `AsyncWltClient` and `await` each method call.

## Configuration

```python
client = WltClient(
    api_key="sk-your-api-key",              # Required. Auto-exchanged for Bearer token
    timeout=30.0,                            # Request timeout in seconds (default: 30)
    max_retries=2,                           # Max retry attempts (default: 2)
)
```

---

## Base URL Configuration

The default base URL is `https://daily.sssxuntui.com`, which targets the public WLT endpoint and requires no configuration. Override it when pointing at a private deployment, a staging environment, or a regional node.

### Configuration Methods

1) **Constructor argument** (highest priority)

```python
from wlt import WltClient

client = WltClient(
    api_key="sk-your-api-key",
    base_url="https://your.host",
)
```

2) **Environment variable**

```bash
export WLT_BASE_URL=https://your.host
```

3) **CLI configuration file** -- the `base_url` field in `~/.wlt/config.json` (CLI users only)

```bash
wlt config set base_url https://your.host
```

### Precedence

| Priority | Source |
|---|---|
| 1 (highest) | Constructor argument / CLI `--base-url` flag |
| 2 | `WLT_BASE_URL` environment variable |
| 3 | `base_url` in `~/.wlt/config.json` (CLI users only) |
| 4 (lowest) | Default `https://daily.sssxuntui.com` |

Trailing slashes are stripped automatically; `https://host/` and `https://host` are equivalent.

### Typical Use Cases

- Private deployment: target a customer-managed gateway
- Staging tests: switch to a test environment during development
- Regional nodes: select the nearest point of presence

---

## Module Reference

| Module | Accessor | Description |
|--------|----------|-------------|
| **Secret** | `client.secrets` | Secret CRUD, usage stats, model/provider queries |
| **ApiKey** | `client.api_keys` | BYOK API key CRUD, status, connectivity test, providers |
| **Usage** | `client.usage` | Serverless list/stats, call history, gateway providers |
| **UserInfo** | `client.user` | Current user info |
| **Billing** | `client.billing` | Monthly billing queries by GPU, AI service, or detail line items |
| **Payment** | `client.payment` | Recharge records, statistics, model usage billing |
| **Model Gallery** | `client.model_gallery` | Model page, providers, prices |
| **Model Lab** | `client.model_lab` | Model list, chat session, stream inference |

---

## Secret

### `client.secrets.list()`

List secrets with pagination and optional filters.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| page_num | int | No | 1 | Page number, starting from 1 |
| page_size | int | No | 10 | Items per page |
| status | int | No | None | Status filter: 1=active, 0=disabled |
| expired_before_utc | datetime | No | None | Expiry time upper bound (UTC), filter secrets expiring before this time |

#### Response

Returns `BaseResponse[SecretPageResultVO]`.

**SecretPageResultVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| totalKeys | int | Total number of secrets |
| activeKeys | int | Number of active secrets |
| totalRequest | int | Total request count |
| avgQps | float | Average QPS |
| pageInfo | PageInfo[SecretVO] | Paginated secret data |

**PageInfo fields:**

| Field | Type | Description |
|-------|------|-------------|
| total | int | Total record count |
| totalPages | int | Total number of pages |
| currentPage | int | Current page number |
| pageSize | int | Items per page |
| list | list[SecretVO] | Data list |

**SecretVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | int | Secret ID |
| name | str | Secret name |
| keyId | str | Key identifier |
| status | str | Status |
| createdAt | datetime | Creation time |
| lastUsed | datetime | Last used time |
| expiredFlag | bool | Whether expired |
| allowedModels | list[str] | Allowed model list |
| allowedProviders | list[str] | Allowed provider list |
| ipWhiteList | list[str] | IP whitelist |
| minuteTokensQuota | int | Per-minute token quota |
| annualTokensQuota | int | Annual token quota |
| description | str | Description |
| expiredAtUtc | datetime | Expiry time (UTC) |
| byokList | list[ApikeyVO] | Associated BYOK key list |
| defaultSecret | int | Whether default secret: 1=yes, 0=no |
| createUser | str | Creator |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

# Basic listing
result = client.secrets.list()
print(f"Total secrets: {result.data.totalKeys}")
for secret in result.data.pageInfo.list:
    print(secret.id, secret.name, secret.status)

# With filters
result = client.secrets.list(page_num=1, page_size=5, status=1)
```

### `client.secrets.detail()`

Get a single secret's details by ID.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| id | int | Yes | -- | Secret ID |

#### Response

Returns `BaseResponse[SecretVO]`.

> SecretVO fields: see `client.secrets.list()` above.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

detail = client.secrets.detail(id=123)
print(detail.data.name)
print(detail.data.allowedModels)
print(detail.data.allowedProviders)
```

### `client.secrets.create()`

Create a new secret. Returns the generated API key string.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| name | str | No | None | Secret name |
| allowed_models | list[str] | No | None | List of allowed model names |
| allowed_providers | list[str] | No | None | List of allowed provider names |
| ip_white_list | list[str] | No | None | IP whitelist (CIDR notation) |
| minute_tokens_quota | int | No | None | Per-minute token quota |
| annual_tokens_quota | int | No | None | Annual token quota |
| expired_at_utc | datetime | No | None | Expiry time in UTC |
| byok_ids | list[int] | No | None | List of BYOK key IDs to associate |

#### Response

Returns `BaseResponse[str]`. The `data` field contains the generated API key string (the secret key value -- save it now, the backend will not return it again).

> **How to obtain the DB `id` after `create()`**
>
> `create()` does not return the database row's numeric `id`, which the
> `edit()` / `action()` (enable/disable/delete) / `usage_by_model()`
> methods require. To resolve the `id`, call
> `client.secrets.list(...)` and match by `name`:
>
> ```python
> created = client.secrets.create(name="my-secret")
> page = client.secrets.list(page_num=1, page_size=100)
> row = next(s for s in page.data.list if s.name == "my-secret")
> secret_id = row.id  # use this for edit / action / usage queries
> ```
>
> A future major version may change `create()` to return the full record;
> until then, the `list()` lookup is the supported pattern.

```python
from wlt import WltClient
from datetime import datetime, timezone, timedelta

client = WltClient(api_key="sk-your-api-key")

# Minimal creation
result = client.secrets.create(name="my-secret")
print(f"New API key: {result.data}")

# Full creation with all options
result = client.secrets.create(
    name="production-key",
    allowed_models=["qwen-turbo", "qwen-plus"],
    allowed_providers=["dashscope"],
    ip_white_list=["10.0.0.0/8"],
    minute_tokens_quota=100000,
    annual_tokens_quota=50000000,
    expired_at_utc=datetime(2027, 1, 1, tzinfo=timezone.utc),
    byok_ids=[1, 2],
)
print(f"New API key: {result.data}")

# To obtain the new secret's DB id, look it up by name via list():
page = client.secrets.list(page_num=1, page_size=100)
new_id = next(s.id for s in page.data.list if s.name == "production-key")
```

### `client.secrets.edit()`

Edit an existing secret.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| id | int | Yes | -- | Secret ID |
| name | str | No | None | Updated name |
| allowed_models | list[str] | No | None | Updated allowed model list |
| allowed_providers | list[str] | No | None | Updated allowed provider list |
| ip_white_list | list[str] | No | None | Updated IP whitelist |
| minute_tokens_quota | int | No | None | Updated per-minute token quota |
| annual_tokens_quota | int | No | None | Updated annual token quota |
| expired_at_utc | datetime | No | None | Updated expiry time (UTC) |
| byok_ids | list[int] | No | None | Updated BYOK key IDs |

#### Response

Returns `BaseResponse[bool]`. `data=True` on success.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.secrets.edit(
    id=123,
    name="updated-secret-name",
    allowed_models=["qwen-turbo", "qwen-max"],
    minute_tokens_quota=200000,
)
print(f"Success: {result.data}")  # True
```

### `client.secrets.action()`

Perform an action on a secret: `"ENABLE"`, `"DISABLE"`, or `"DELETE"`.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| id | int | Yes | -- | Secret ID |
| status | str | Yes | -- | Action: `"ENABLE"`, `"DISABLE"`, or `"DELETE"` |

#### Response

Returns `BaseResponse[bool]`. `data=True` on success.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

# Disable a secret
result = client.secrets.action(id=123, status="DISABLE")
print(f"Success: {result.data}")  # True

# Enable a secret
result = client.secrets.action(id=123, status="ENABLE")

# Delete a secret
result = client.secrets.action(id=123, status="DELETE")
```

### `client.secrets.usage_by_model()`

Query secret usage breakdown by model with pagination.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| secret_id | int | Yes | -- | Secret ID |
| start_time | datetime | No | None | Query start time |
| end_time | datetime | No | None | Query end time |
| page_num | int | No | 1 | Page number |
| page_size | int | No | 10 | Items per page |

#### Response

Returns `BaseResponse[PageInfo[SecretModelUsageVO]]`.

**SecretModelUsageVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| model | str | Model name |
| requests | int | Request count |
| totalTokens | int | Total token consumption |
| promptTokens | int | Prompt token count |
| completionTokens | int | Completion token count |

```python
from wlt import WltClient
from datetime import datetime

client = WltClient(api_key="sk-your-api-key")

result = client.secrets.usage_by_model(
    secret_id=123,
    start_time=datetime(2026, 4, 1),
    end_time=datetime(2026, 4, 21),
    page_num=1,
    page_size=10,
)
for item in result.data.list:
    print(item.model, item.promptTokens, item.completionTokens)
```

### `client.secrets.model_list()`

Query available models for secret binding.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| model_name | str | No | None | Model name (fuzzy search) |
| provider | str | No | None | Provider identifier |
| series_provider | str | No | None | Series provider identifier |
| model_type | str | No | None | Model type |
| view_all_flag | int | No | None | View all flag (1=view all) |
| page_size | int | No | None | Items per page |
| page_num | int | No | None | Page number |
| usage_type | str | No | None | Usage type: `"inference"` or `"train"` |
| training_method | str | No | None | Training method: `"sft"` or `"dpo"` |
| tags | list[str] | No | None | Tag filter list, e.g. `["New"]` |
| capability | str | No | None | Capability filter, e.g. `"text_to_text"` |
| model_type_list | list[str] | No | None | Model type list (multi-select) |
| origin_providers | list[str] | No | None | Origin provider list (multi-select) |
| inputs | list[str] | No | None | Input type list, e.g. `["text"]` |
| outputs | list[str] | No | None | Output type list, e.g. `["image"]` |

#### Response

Returns `BaseResponse[list[ModelInfoVO]]`.

**ModelInfoVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| modelId | str | Model ID |
| modelName | str | Model name |
| modelImage | str | Model icon URL |
| isNew | int | Whether new model (1=yes) |
| provider | str | Provider identifier |
| providerName | str | Provider display name |
| originProvider | str | Origin provider identifier |
| seriesProvider | str | Series provider identifier |
| modelType | str | Model type |
| tags | list[str] | Tag list |
| modelSize | str | Model size |
| feature | str | Feature description |
| price | Decimal | Price |
| unit | str | Price unit |
| available | bool | Whether available |
| updated | datetime | Update time |
| deployPlatform | str | Deploy platform |
| regionId | str | Region ID |
| deployFrameworks | list[str] | Deploy framework list |
| labels | list[str] | Label list |
| capabilities | list[str] | Capability list (e.g. `text_to_text`) |
| inputs | list[str] | Input type set |
| outputs | list[str] | Output type set |
| priceExtend | PartnerApiPriceExtend | Extended price info |
| dataSource | str | Data source (`model_card` or `model_router`) |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

# List all inference models
models = client.secrets.model_list(usage_type="inference")
for m in models.data:
    print(m.modelName, m.provider)

# Filter by provider
models = client.secrets.model_list(
    provider="dashscope",
    model_name="qwen",
    tags=["New"],
)
for m in models.data:
    print(m.modelName)
```

### `client.secrets.providers()`

Get the list of providers available for secrets.

#### Parameters

None.

#### Response

Returns `BaseResponse[list[ProviderVO]]`.

**ProviderVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | str | Provider identifier |
| name | str | Provider display name |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

providers = client.secrets.providers()
for p in providers.data:
    print(p.id, p.name)
```

---

## ApiKey (BYOK)

### `client.api_keys.list()`

List API keys with pagination and optional filters.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| page_num | int | No | 1 | Page number, starting from 1 |
| page_size | int | No | 10 | Items per page |
| provider | str | No | None | Filter by provider |
| timezone | str | No | None | Timezone for time field localization, e.g. `"Asia/Shanghai"` |

#### Response

Returns `BaseResponse[ApiKeyResultVO]`.

**ApiKeyResultVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| totalApiCalls | int | Total API call count |
| activeKeys | int | Number of active keys |
| apiKeys | PageInfo[ApikeyVO] | Paginated API key data |

**ApikeyVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | str | ApiKey ID |
| name | str | Key name |
| key | str | Key content (masked) |
| provider | str | Provider name |
| providerId | str | Provider ID |
| account | str | Account info |
| accountId | str | Account ID |
| projectID | str | Project ID |
| created | datetime | Creation time |
| lastUsedAt | datetime | Last used time |
| status | str | Status |
| accessKeyID | str | AccessKey ID (masked) |
| accessKeySecret | str | AccessKey Secret (masked) |
| resaleStatus | str | Resale status |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.api_keys.list(page_num=1, page_size=10)
for key in result.data.apiKeys.list:
    print(key.id, key.name, key.provider, key.status)

# Filter by provider
result = client.api_keys.list(provider="dashscope")
```

### `client.api_keys.create()`

Create a new BYOK API key.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| provider | str | Yes | -- | Provider **id** (NOT display name); use the `id` field returned by `api_keys.providers()`, e.g. `"dashscope"`, `"openrouter"`. |
| key_name | str | No | None | Key name |
| status | int | No | None | Initial status |
| access_key_id | str | No | None | AccessKey ID |
| access_key_secret | str | No | None | AccessKey Secret |
| tpm_limit | str | No | None | TPM (tokens per minute) limit |
| token_limit | str | No | None | Total token limit |

> **Tip:** `provider` MUST be the `id` from `api_keys.providers()` (e.g. `dashscope`), NOT the display `name` (e.g. `"Alibaba Cloud Model Studio - BJ"`). Passing the display name returns `code=3000 "Invalid provider"`.

#### Response

Returns `BaseResponse[ApikeyVO]`. The `data` field contains the newly created API key info.

> ApikeyVO fields: see `client.api_keys.list()` above.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

new_key = client.api_keys.create(
    provider="dashscope",
    key_name="my-dashscope-key",
    access_key_id="LTAI5t...",
    access_key_secret="your-access-key-secret",
    tpm_limit="100000",
    token_limit="5000000",
)
print(f"Created key ID: {new_key.data.id}")
print(f"Key name: {new_key.data.name}")
```

### `client.api_keys.update()`

Update an existing API key.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| path_id | str | Yes | -- | ApiKey ID (URL path parameter, for routing) |
| id | int | No | None | ApiKey database primary key (used for locating the record) |
| provider | str | No | None | Provider (must be a valid BYOK provider if provided) |
| key_name | str | No | None | Key name |
| aliyun_account_id | str | No | None | Aliyun account ID |
| status | int | No | None | Status |
| access_key_id | str | No | None | AccessKey ID |
| access_key_secret | str | No | None | AccessKey Secret |
| resale_status | str | No | None | Resale status |
| tpm_limit | str | No | None | TPM limit |
| token_limit | str | No | None | Total token limit |

#### Response

Returns `BaseResponse[bool]`. `data=True` on success.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.api_keys.update(
    path_id="456",
    id=456,
    key_name="renamed-key",
    tpm_limit="200000",
)
print(f"Success: {result.data}")  # True
```

### `client.api_keys.delete()`

Delete an API key.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| id | str | Yes | -- | ApiKey ID |

#### Response

Returns `BaseResponse[bool]`. `data=True` on success.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.api_keys.delete(id="456")
print(f"Success: {result.data}")  # True
```

### `client.api_keys.change_status()`

Enable or disable an API key.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| id | str | Yes | -- | ApiKey ID |
| status | int | Yes | -- | Target status: 1=enable, 0=disable |

#### Response

Returns `BaseResponse[bool]`. `data=True` on success.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

# Disable a key (status: 0=disable, 1=enable)
result = client.api_keys.change_status(id="456", status=0)
print(f"Success: {result.data}")  # True

# Enable a key
result = client.api_keys.change_status(id="456", status=1)
```

### `client.api_keys.test_connect()`

Test API key connectivity by relaying the credentials to the upstream provider.

> **Behaviour on bad creds:** the call returns successfully (no exception) but `r.data` may be `False`, or the SDK may surface a typed `APIError` wrapping the upstream's 401. Both outcomes mean the SDK is working correctly — only network/auth-against-our-backend failures should be treated as SDK bugs.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| aliyun_account_id | str | No | None | Aliyun account ID (for providers that require it) |
| access_key_id | str | No | None | AccessKey ID |
| access_key_secret | str | No | None | AccessKey Secret |
| provider | str | No | None | Provider name |

#### Response

Returns `BaseResponse[bool]`. `data=True` if connection is successful.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.api_keys.test_connect(
    provider="dashscope",
    access_key_id="LTAI5t...",
    access_key_secret="your-access-key-secret",
)
if result.data:
    print("Connection successful!")
else:
    print("Connection failed")
```

### `client.api_keys.providers()`

Get the list of providers available for API keys.

#### Parameters

None.

#### Response

Returns `BaseResponse[list[ProviderVO]]`.

**ProviderVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | str | Provider identifier |
| name | str | Provider display name |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

providers = client.api_keys.providers()
for p in providers.data:
    print(p.id, p.name)
```

---

## Usage

### `client.usage.serverless_list()`

Query serverless usage list with filters.

> **`start_time` / `end_time` are REQUIRED.** The backend NPEs when either is
> missing. The SDK raises a client-side
> `ValueError("start_time and end_time are required")` so you get a usable
> error before the network call.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| start_time | datetime | Yes | -- | Query start time |
| end_time | datetime | Yes | -- | Query end time |
| page_num | int | No | 1 | Page number (>= 1) |
| page_size | int | No | 10 | Items per page (>= 1) |
| api_key | str | No | None | Filter by API Key |
| inputs | list[str] | No | None | Input type filter list |
| outputs | list[str] | No | None | Output type filter list |
| model_ids | list[str] | No | None | Model ID filter list |

#### Response

Returns `BaseResponse[PageInfo[ServerlessUsageRecord]]`.

**ServerlessUsageRecord fields:**

| Field | Type | Description |
|-------|------|-------------|
| modelName | str | Model name |
| apiKeyName | str | API Key name |
| requests | str | Request count |
| totalTokens | str | Total token consumption |
| avgLatency | str | Average latency |
| successRate | str | Success rate (%) |
| errorCount | str | Error count |
| totalFee | str | Total fee |
| throughput | str | Throughput (QPS) |
| usageStats | str | Usage statistics info |

```python
from wlt import WltClient
from datetime import datetime

client = WltClient(api_key="sk-your-api-key")

result = client.usage.serverless_list(
    start_time=datetime(2026, 4, 1),
    end_time=datetime(2026, 4, 21),
    page_num=1,
    page_size=20,
    model_ids=["qwen-turbo"],
)
for record in result.data.list:
    print(record.modelName, record.totalTokens)
```

### `client.usage.serverless_stats()`

Get serverless usage aggregated statistics.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| start_time | datetime | Yes | -- | Query start time |
| end_time | datetime | Yes | -- | Query end time |
| page_num | int | No | 1 | Page number |
| page_size | int | No | 10 | Items per page |
| api_key | str | No | None | Filter by API Key |
| inputs | list[str] | No | None | Input type filter list |
| outputs | list[str] | No | None | Output type filter list |
| model_ids | list[str] | No | None | Model ID filter list |

#### Response

Returns `BaseResponse[ServerlessStatsResult]`.

**ServerlessStatsResult fields:**

| Field | Type | Description |
|-------|------|-------------|
| totalRequests | int | Total request count |
| requestTrend | str | Request trend |
| totalTokens | int | Total token consumption |
| tokenTrend | str | Token trend |
| totalUseModel | int | Total number of models used |
| totalUseModelTrend | str | Model usage trend |
| avgLatency | str | Average latency |
| avgLatencyTrend | str | Average latency trend |

```python
from wlt import WltClient
from datetime import datetime

client = WltClient(api_key="sk-your-api-key")

stats = client.usage.serverless_stats(
    start_time=datetime(2026, 4, 1),
    end_time=datetime(2026, 4, 21),
    model_ids=["qwen-turbo", "qwen-plus"],
)
print(stats.data)
```

### `client.usage.history()`

Query API call history with multi-dimension filters.

> **`start_time` / `end_time` are REQUIRED** (backend NPEs if missing; the SDK
> raises `ValueError` client-side).

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| provider | str | No | None | Provider filter |
| feature | str | No | None | Feature module filter |
| request_type | str | No | None | Request type filter |
| start_time | datetime | **Yes** | -- | Query start time (REQUIRED) |
| end_time | datetime | **Yes** | -- | Query end time (REQUIRED) |
| keyword | str | No | None | Keyword search |
| page_num | int | No | None | Page number |
| page_size | int | No | None | Items per page |
| model_ids | list[str] | No | None | Model ID filter list |
| inputs | list[str] | No | None | Input type filter list |
| outputs | list[str] | No | None | Output type filter list |

#### Response

Returns `BaseResponse[PageInfo[HistoryRecord]]`.

**HistoryRecord fields:**

| Field | Type | Description |
|-------|------|-------------|
| requestId | str | Request ID |
| modelNameVersion | str | Model name and version |
| statusCode | int | HTTP status code |
| latency | float | Gateway latency (ms) |
| totalTokens | int | Total token count |
| timestamp | datetime | Request time |
| requestType | str | Request type |
| modelTechnology | str | Model technology |
| promptTokens | int | Input token count |
| completionTokens | int | Output token count |
| providerTtft | float | TTFT (time to first token, ms) |
| providerLatency | float | Provider latency (ms) |
| providerResponseType | str | Response type |
| apiKeyName | str | API Key name |
| gatewayRequestBodySize | int | Request body size (bytes) |
| errorMsg | str | Error message |

```python
from wlt import WltClient
from datetime import datetime

client = WltClient(api_key="sk-your-api-key")

result = client.usage.history(
    start_time=datetime(2026, 4, 1),
    end_time=datetime(2026, 4, 21),
    provider="dashscope",
    page_num=1,
    page_size=20,
)
for record in result.data.list:
    print(record.modelNameVersion, record.requestType, record.timestamp)

# Search by keyword within a time window (start/end remain required)
result = client.usage.history(
    keyword="qwen",
    start_time=datetime(2026, 4, 1),
    end_time=datetime(2026, 4, 21),
    page_num=1,
    page_size=10,
)
```

### `client.usage.gateway_providers()`

Get gateway provider list for usage filtering.

#### Parameters

None.

#### Response

Returns `BaseResponse[list[ProviderVO]]`.

**ProviderVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | str | Provider identifier |
| name | str | Provider display name |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

providers = client.usage.gateway_providers()
for p in providers.data:
    print(p.id, p.name)
```

---

## User

### `client.user.info()`

Get the current logged-in user's information.

#### Parameters

None.

#### Response

Returns `BaseResponse[LoginUser]`.

**LoginUser fields:**

| Field | Type | Description |
|-------|------|-------------|
| userId | int | User ID |
| aliyunPk | str | Aliyun PK info |
| parentPk | str | Parent account PK info |
| accountStructure | int | Account type: 1=Partner, 2=Customer main account, 3=SubUser, 4=TokenLogin |
| cmUserId | str | CM user ID |
| tenantCode | str | Tenant code |
| userName | str | Username |
| bucId | int | BUC ID |
| bid | str | BID |
| needAuth | bool | Whether SLR authorization is needed |
| parentUserId | int | Parent account database ID |
| securityToken | str | Security token |
| accessKeyId | str | AccessKey ID |
| timezone | str | Timezone info |
| region | str | Region info |
| userPermission | UserPermissionResponse | User permission info |
| userType | str | User type (e.g. `PartnerUser` / `PartnerUserSub`) |
| partnerId | int | Tenant ID |
| partnerName | str | Tenant name |
| partnerLogoUrl | str | Tenant logo URL |
| sourceExternalTicket | str | SSO external ticket |
| thirdChannel | str | Third-party channel source |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

user = client.user.info()
print(f"User ID: {user.data.userId}")
print(f"Username: {user.data.userName}")
```

---

## Billing

### `client.billing.query_all_amount()`

Query total billing amount for a given billing cycle.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| billing_cycle | str | Yes | -- | Billing cycle, format `"YYYY-MM"` (e.g. `"2026-04"`) |

#### Response

Returns `BaseResponse[UserBillingMonthResponse]`.

**UserBillingMonthResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| afterDiscountAmountSum | Decimal | Amount after discount |
| deductedByCouponsSum | Decimal | Coupon deduction amount |
| pretaxGrossAmountSum | Decimal | Original amount (list price) |
| pretaxAmountSum | Decimal | Payable amount |
| currency | str | Currency code (e.g. `"USD"`) |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

bill = client.billing.query_all_amount(billing_cycle="2026-04")
print(f"Total amount: {bill.data.pretaxAmountSum}")
print(f"Currency: {bill.data.currency}")
```

### `client.billing.query_bill_by_gpu()`

Query billing details by GPU spec with pagination.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| billing_cycle | str | Yes | -- | Billing cycle, format `"YYYY-MM"` |
| page_size | int | No | 10 | Items per page |
| page_num | int | No | 1 | Page number, starting from 1 |

#### Response

Returns `BaseResponse[PageInfo[UserBillingGpuServiceResponse]]`.

**UserBillingGpuServiceResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| afterDiscountAmountSum | Decimal | Amount after discount |
| deductedByCouponsSum | Decimal | Coupon deduction amount |
| pretaxGrossAmountSum | Decimal | Original amount |
| pretaxAmountSum | Decimal | Payable amount |
| listPrice | Decimal | List price (unit price) |
| currency | str | Currency code |
| gpuSpec | str | GPU spec (e.g. `"A100-80G"`) |
| servicePeriodUnit | str | Service period unit (e.g. `"Hour"`) |
| servicePeriodSum | str | Total service period |
| modelResource | str | Model resource identifier |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.billing.query_bill_by_gpu(
    billing_cycle="2026-04",
    page_size=10,
    page_num=1,
)
for item in result.data.list:
    print(f"GPU: {item.gpuSpec}, Amount: {item.pretaxAmountSum}")
```

### `client.billing.query_bill_by_service()`

Query billing details by AI service with pagination.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| billing_cycle | str | Yes | -- | Billing cycle, format `"YYYY-MM"` |
| page_size | int | No | 10 | Items per page |
| page_num | int | No | 1 | Page number, starting from 1 |

#### Response

Returns `BaseResponse[PageInfo[UserBillingAiServiceResponse]]`.

**UserBillingAiServiceResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| afterDiscountAmountSum | Decimal | Amount after discount |
| deductedByCouponsSum | Decimal | Coupon deduction amount |
| pretaxGrossAmountSum | Decimal | Original amount |
| pretaxAmountSum | Decimal | Payable amount |
| currency | str | Currency code |
| gpuSpec | str | GPU spec |
| modelResource | str | Model resource identifier |
| servicePeriodUnit | str | Service period unit |
| servicePeriodSum | str | Total service period |
| bizType | str | Business type (e.g. `"Deploy"`, `"Finetune"`) |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.billing.query_bill_by_service(
    billing_cycle="2026-04",
    page_size=10,
    page_num=1,
)
for item in result.data.list:
    print(f"Service: {item.bizType}, Amount: {item.pretaxAmountSum}")
```

### `client.billing.query_bill_list()`

Query billing detail list with pagination.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| billing_cycle | str | Yes | -- | Billing cycle, format `"YYYY-MM"` |
| page_size | int | No | 10 | Items per page |
| page_num | int | No | 1 | Page number, starting from 1 |

#### Response

Returns `BaseResponse[PageInfo[UserBillingResponse]]`.

**UserBillingResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| afterDiscountAmount | Decimal | Amount after discount |
| deductedByCoupons | Decimal | Coupon deduction amount |
| pretaxGrossAmount | Decimal | Original amount |
| pretaxAmount | Decimal | Payable amount |
| instanceID | str | Instance ID |
| currency | str | Currency code |
| productCode | str | Product code |
| region | str | Region |
| gpuSpec | str | GPU spec |
| modelResource | str | Model resource identifier |
| servicePeriodUnit | str | Service period unit |
| servicePeriod | str | Service period |
| bizType | str | Business type (e.g. `"Deploy"`, `"Finetune"`) |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.billing.query_bill_list(
    billing_cycle="2026-04",
    page_size=20,
    page_num=1,
)
print(f"Total records: {result.data.total}")
for item in result.data.list:
    print(item)
```

---

## Payment

### `client.payment.records()`

Query recharge records with pagination.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| page_num | int | No | 1 | Page number, starting from 1 |
| page_size | int | No | 10 | Items per page (max 1000) |
| status_list | list[str] | No | None | Status filter: `"0"`=processing, `"1"`=success, `"2"`=failed, `"3"`=closed |
| start_time | datetime | No | None | Query start time |
| end_time | datetime | No | None | Query end time |
| transaction_type | str | No | None | Transaction type: `"Recharge"` or `"Refund"` |

#### Response

Returns `BaseResponse[PageResponse]` where the `list` field contains `RechargeTransactionResponse` items.

**PageResponse fields (Payment-specific pagination):**

| Field | Type | Description |
|-------|------|-------------|
| pageNum | int | Current page number |
| pageSize | int | Items per page |
| total | int | Total record count |
| pages | int | Total number of pages |
| hasNext | bool | Whether there is a next page |
| hasPrevious | bool | Whether there is a previous page |
| list | list | Data list |

**RechargeTransactionResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | int | Record primary key ID |
| createdAtUtc | str | Creation time (UTC) |
| updatedAtUtc | str | Update time (UTC) |
| userId | int | User ID |
| payChannel | str | Payment channel (e.g. `"Stripe"`) |
| payType | str | Payment type (`APP` / `WEB`) |
| transactionType | str | Transaction type (`Recharge` / `Refund`) |
| status | str | Status: 0=processing, 1=success, 2=failed, 3=closed |
| callbackAtUtc | str | Callback time (UTC) |
| transactionNo | str | Platform transaction number |
| externalNo | str | External transaction number (Stripe ID) |
| amount | str | Transaction amount |
| refundAmount | str | Refunded amount |
| currency | str | Currency (ISO 4217) |
| refundStatus | str | Refund status: 0=none, 1=partial, 2=full |
| originalTransactionNo | str | Original transaction number (for refund records) |
| extend | str | Extension field (JSON) |

```python
from wlt import WltClient
from datetime import datetime

client = WltClient(api_key="sk-your-api-key")

# List successful recharge records
result = client.payment.records(
    page_num=1,
    page_size=10,
    status_list=["1"],  # 0=processing, 1=success, 2=failed, 3=closed
)
print(f"Total: {result.data.total}")
for record in result.data.list:
    print(record.transactionNo, record.amount, record.status)

# With time range and transaction type
result = client.payment.records(
    start_time=datetime(2026, 4, 1),
    end_time=datetime(2026, 4, 21),
    transaction_type="Recharge",
)
```

### `client.payment.statistics()`

Query recharge statistics summary.

#### Parameters

None.

#### Response

Returns `BaseResponse[RechargeStatisticsResponse]`.

**RechargeStatisticsResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| totalRechargedAmount | str | Total recharged amount |
| totalUsedRechargedAmount | str | Total used recharged amount |
| totalBalanceRechargedAmount | str | Unused recharge balance |
| totalRefundAmount | str | Total refund amount |
| totalFreeAmount | str | Free quota amount |
| currency | str | Currency code |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

stats = client.payment.statistics()
print(f"Total recharged: {stats.data.totalRechargedAmount}")
```

### `client.payment.get_record()`

Get a single recharge record by transaction number.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| transaction_no | str | Yes | -- | Platform transaction number |

#### Response

Returns `BaseResponse[RechargeTransactionResponse]`.

> RechargeTransactionResponse fields: see `client.payment.records()` above.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

record = client.payment.get_record(transaction_no="TXN20260421001")
print(f"Amount: {record.data.amount}")
print(f"Status: {record.data.status}")
print(f"Created: {record.data.createdAtUtc}")
```

### `client.payment.billing_list()`

Query model usage billing list with pagination.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| page_num | int | No | 1 | Page number, starting from 1 |
| page_size | int | No | 10 | Items per page (max 1000) |
| start_time | str | No | None | Start time, format `"yyyy-MM-dd HH:mm:ss"` |
| end_time | str | No | None | End time, format `"yyyy-MM-dd HH:mm:ss"` |
| model_id | str | No | None | Model ID for exact filtering |

#### Response

Returns `BaseResponse[PageInfo[LmModelUsageBillingResponse]]`.

**LmModelUsageBillingResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | int | Record primary key ID |
| createdAtUtc | str | Record creation time (UTC) |
| userId | int | User ID |
| username | str | Username |
| modelName | str | Model name |
| modelType | str | Model type |
| feeType | str | Fee type (e.g. `"input tokens"` / `"output tokens"`) |
| unitPrice | str | Unit price (per 1K tokens) |
| currency | str | Currency (ISO 4217) |
| usedAmout | str | Usage amount (token count). Note: field name matches backend spelling |
| amount | str | Billed amount |
| billingTimeStart | str | Billing start time |
| billingTimeEnd | str | Billing end time |
| extend | str | Extension field (JSON) |
| inputs | set[str] | Input type list |
| outputs | set[str] | Output type list |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

result = client.payment.billing_list(
    page_num=1,
    page_size=10,
    start_time="2026-04-01 00:00:00",
    end_time="2026-04-21 23:59:59",
)
print(f"Total: {result.data.total}")
for item in result.data.list:
    print(item.modelName, item.amount)

# Filter by model
result = client.payment.billing_list(
    start_time="2026-04-01 00:00:00",
    end_time="2026-04-21 23:59:59",
    model_id="qwen-turbo",
)
```

### `client.payment.billing_query_all_amount()`

Query model usage billing total amount.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| start_time | str | No | None | Start time, format `"yyyy-MM-dd HH:mm:ss"` |
| end_time | str | No | None | End time, format `"yyyy-MM-dd HH:mm:ss"` |
| model_id | str | No | None | Model ID for exact filtering |

#### Response

Returns `BaseResponse[ModelUsageBillingAmountResponse]`.

**ModelUsageBillingAmountResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| totalBillingAmount | str | Total billing amount |
| currency | str | Currency code |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

total = client.payment.billing_query_all_amount(
    start_time="2026-04-01 00:00:00",
    end_time="2026-04-21 23:59:59",
)
print(f"Total billing amount: {total.data.totalBillingAmount}")

# Filter by specific model
total = client.payment.billing_query_all_amount(
    start_time="2026-04-01 00:00:00",
    end_time="2026-04-21 23:59:59",
    model_id="qwen-turbo",
)
```

---

## Model Gallery

### `client.model_gallery.page()`

Query model list with pagination.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| page_num | int | No | 1 | Page number, starting from 1 |
| page_size | int | No | 10 | Items per page |
| model_name | str | No | None | Model name (fuzzy search) |
| provider | str | No | None | Provider identifier |
| series_provider | str | No | None | Series provider identifier |
| model_type | str | No | None | Model type |
| view_all_flag | int | No | None | View all flag (1=view all) |
| usage_type | str | No | None | Usage type: `"inference"` or `"train"` |
| training_method | str | No | None | Training method: `"sft"` or `"dpo"` |
| tags | list[str] | No | None | Tag filter list |
| capability | str | No | None | Capability filter, e.g. `"text_to_text"` |
| model_type_list | list[str] | No | None | Model type list (multi-select) |
| origin_providers | list[str] | No | None | Origin provider list (multi-select) |
| inputs | list[str] | No | None | Input type list, e.g. `["text"]` |
| outputs | list[str] | No | None | Output type list, e.g. `["image"]` |

#### Response

Returns `BaseResponse[PageInfo[ModelInfoVO]]`.

> ModelInfoVO fields: see `client.secrets.model_list()` above.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

# Browse all models
result = client.model_gallery.page(page_num=1, page_size=10)
print(f"Total models: {result.data.total}")
for model in result.data.list:
    print(model.modelName, model.provider)

# Filter by criteria
result = client.model_gallery.page(
    usage_type="inference",
    provider="dashscope",
    tags=["New"],
    inputs=["text"],
    outputs=["text"],
)
```

### `client.model_gallery.providers()`

Get the list of model providers.

#### Parameters

None.

#### Response

Returns `BaseResponse[list[ProviderVO]]`.

**ProviderVO fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | str | Provider identifier |
| name | str | Provider display name |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

providers = client.model_gallery.providers()
for p in providers.data:
    print(p.id, p.name)
```

### `client.model_gallery.get_price()`

Get model API price for the current tenant.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| model_name | str | Yes | -- | Model name |

#### Response

Returns `BaseResponse[PartnerApiPriceResponse]`.

**PartnerApiPriceResponse fields:**

| Field | Type | Description |
|-------|------|-------------|
| id | int | Record ID |
| partnerId | int | Tenant ID |
| modelName | str | Model name |
| modelType | str | Model type |
| type | str | Resale type (model / BYOK) |
| originProvider | str | Origin provider |
| seriesProvider | str | Series provider |
| capabilities | str | Model capabilities |
| inputs | set[str] | Input type set |
| outputs | set[str] | Output type set |
| priceExtend | PartnerApiPriceExtendDetail | Price details |
| gmtCreate | datetime | Creation time |
| gmtModified | datetime | Modification time |

**PartnerApiPriceExtendDetail fields:**

| Field | Type | Description |
|-------|------|-------------|
| llmInput | str | LLM input price (per 1K tokens) |
| llmOutput | str | LLM output price (per 1K tokens) |
| vlmInput | str | VLM input price (per 1K tokens) |
| vlmOutput | str | VLM output price (per 1K tokens) |
| image | str | Image generation price |
| imageEa | str | Image unit price |
| imageStep | str | Image step price |
| video | str | Video generation price |
| audio | str | Audio price |
| tieredPricing | list[TieredPricing] | Tiered pricing list |
| videoPricing | list[VideoPricing] | Video pricing list |
| imagePricing | list[ImagePricing] | Image pricing list |

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

price = client.model_gallery.get_price(model_name="Qwen2.5-72B-Instruct")
print(f"Input price: {price.data.priceExtend.llmInput}")
print(f"Output price: {price.data.priceExtend.llmOutput}")
```

---

## Model Lab

### `client.model_lab.list()`

Get model list for the Model Lab.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| model_name | str | No | None | Model name (fuzzy search) |
| provider | str | No | None | Provider identifier |
| series_provider | str | No | None | Series provider identifier |
| model_type | str | No | None | Model type |
| view_all_flag | int | No | None | View all flag (1=view all) |
| page_size | int | No | None | Items per page. **Required by backend** (code=1000 if missing). Recommended default: 20 |
| page_num | int | No | None | Page number. **Required by backend** (code=1000 if missing). Recommended default: 1 |
| usage_type | str | No | None | Usage type: `"inference"` or `"train"` |
| training_method | str | No | None | Training method: `"sft"` or `"dpo"` |
| tags | list[str] | No | None | Tag filter list |
| capability | str | No | None | Capability filter, e.g. `"text_to_text"` |
| model_type_list | list[str] | No | None | Model type list (multi-select) |
| origin_providers | list[str] | No | None | Origin provider list (multi-select) |
| inputs | list[str] | No | None | Input type list, e.g. `["text"]` |
| outputs | list[str] | No | None | Output type list (max 1 item), e.g. `["image"]` |

#### Response

Returns `BaseResponse[list[ModelInfoVO]]`.

> ModelInfoVO fields: see `client.secrets.model_list()` above.

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

# List inference models
models = client.model_lab.list(usage_type="inference")
for m in models.data:
    print(m.modelName, m.provider)

# Filter by provider and capability
models = client.model_lab.list(
    provider="dashscope",
    capability="text_to_text",
    inputs=["text"],
    outputs=["text"],
)
```

### `client.model_lab.create_session()`

Create a new chat session for multi-turn conversations.

#### Parameters

None.

#### Response

Returns `BaseResponse[str]`. The `data` field contains a UUID string (session ID).

```python
from wlt import WltClient

client = WltClient(api_key="sk-your-api-key")

session = client.model_lab.create_session()
session_id = session.data  # UUID string
print(f"Session ID: {session_id}")
```

### `client.model_lab.stream()`

Call model stream inference (SSE). Yields each SSE data chunk as a raw JSON string.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| provider | str | Yes | -- | Provider identifier |
| model_name | str | Yes | -- | Model name |
| user_prompt | str | Yes (unless retry) | None | User prompt text |
| session_id | str | No | None | Session ID (for multi-turn conversations, use UUID) |
| task_id | str | No | None | Task ID (for retry, references the failed task) |
| task_type | str | No | `"TEXT"` | Task type |
| is_retry | bool | No | False | Whether this is a retry request (requires task_id) |
| options | OptionsDTO | No | None | Model parameter configuration (see below) |

**OptionsDTO fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| temperature | float | No | None | Temperature, controls output randomness |
| maxToken | int | No | None | Maximum generated token count |
| topP | float | No | None | Top P sampling parameter |
| topK | int | No | None | Top K sampling parameter |
| presencePenalty | float | No | None | Presence penalty |
| frequencyPenalty | float | No | None | Frequency penalty |
| stopSequences | str | No | None | Stop sequences |
| systemPrompt | str | No | None | System prompt |
| enableThinking | bool | No | None | Enable deep thinking |
| enableProgress | bool | No | None | Enable progress push |
| timeoutSeconds | int | No | None | Task timeout (seconds) |
| enableCache | bool | No | None | Enable result caching |
| enableDocumentInlining | bool | No | None | Enable document inlining |
| budgetTokens | int | No | None | Budget token count |

#### Response

Returns a generator yielding SSE data chunks as raw JSON strings. Each chunk follows the OpenAI-compatible streaming format. The stream ends with a `[DONE]` sentinel.

```python
import json
from wlt import WltClient
from wlt.models.model_lab import OptionsDTO

client = WltClient(api_key="sk-your-api-key")

# Create a session for multi-turn conversation
session = client.model_lab.create_session()

# Stream inference (use a provider + model that is actually served by the
# gateway -- discover via `client.model_lab.list()` / `client.usage.gateway_providers()`.
# On the daily environment, `openrouter` + `openai/gpt-5.4-nano` is a known-good pair).
for chunk in client.model_lab.stream(
    provider="openrouter",
    model_name="openai/gpt-5.4-nano",
    user_prompt="Explain quantum computing in one paragraph.",
    session_id=session.data,
    options=OptionsDTO(
        temperature=0.7,
        maxToken=2048,
        topP=0.9,
        systemPrompt="You are a helpful assistant.",
    ),
):
    data = json.loads(chunk)
    # Each chunk contains delta content, finish_reason, usage, etc.
    if "content" in data:
        print(data["content"], end="", flush=True)

print()  # newline after streaming completes
```

---

## Error Handling

All API methods can raise typed exceptions. The SDK auto-refreshes the Bearer token whenever the server returns an authentication failure -- HTTP `401` / `403` or a business code in `{2000, 2002, 2007, 3001}` (see `_AUTH_ERROR_CODES` in `src/wlt/_http.py`). `AuthenticationError` is only raised if the refresh attempt also fails.

All exception classes are re-exported from the package root, so you can
`from wlt import WltError` (no need to import from `wlt._exceptions`):

```python
from wlt import (
    WltClient,
    # Exception classes (also available as `from wlt._exceptions import ...`
    # for backwards compatibility, but the top-level import is preferred).
    WltError,
    APIError,
    AuthenticationError,
    PermissionError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    InternalError,
    ConnectionError,
    TimeoutError,
)

client = WltClient(api_key="sk-your-api-key")

try:
    result = client.secrets.list()
except AuthenticationError as e:
    # code 2000/2002: API Key invalid; code 3001: token expired (auto-refresh failed)
    print(f"Auth failed (code={e.code}): {e.message}")
except PermissionError as e:
    print(f"Permission denied (code={e.code}): {e.message}")
except NotFoundError as e:
    print(f"Not found (code={e.code}): {e.message}")
except ValidationError as e:
    print(f"Invalid params (code={e.code}): {e.message}")
except RateLimitError as e:
    print(f"Rate limited (code={e.code}): {e.message}")
except InternalError as e:
    print(f"Server error (code={e.code}): {e.message}")
except APIError as e:
    # Catch-all for any other business error code
    print(f"API error (code={e.code}): {e.message}")
except ConnectionError as e:
    print(f"Network error: {e.message}")
except TimeoutError as e:
    print(f"Request timeout: {e.message}")
```

### Error Code Reference

| Code | Exception | Description |
|------|-----------|-------------|
| 200 | _(success)_ | OK |
| 1000 | `InternalError` | Server internal error |
| 1001 | `ValidationError` | Invalid request parameters |
| 1002 | `NotFoundError` | Resource not found |
| 2000 | `AuthenticationError` | Login failure |
| 2002 | `AuthenticationError` | Authentication error |
| 2007 | `PermissionError` | Permission denied |
| 3000 | `APIError` | Invalid request (observed in production on Model Gallery detail with non-numeric id) |
| 3001 | `AuthenticationError` | Token expired / invalid |
| 3002 | `RateLimitError` | Rate limit exceeded |

---

## Provider Values

Valid provider values for Secret `allowedProviders` and ApiKey operations:

- `dashscope`
- `dashscope-intl`
- `dashscope-us`
- `openrouter`
- `zenlayer`

Use `client.secrets.providers()`, `client.api_keys.providers()`, or
`client.model_gallery.providers()` to retrieve the latest available providers dynamically.

---

## Available Services Summary (35 Methods)

| Service | Field | Methods |
|---------|-------|---------|
| User | `client.user` | `info` |
| Secrets | `client.secrets` | `list`, `detail`, `create`, `edit`, `action`, `usage_by_model`, `model_list`, `providers` |
| ApiKeys | `client.api_keys` | `list`, `create`, `update`, `delete`, `change_status`, `test_connect`, `providers` |
| Usage | `client.usage` | `serverless_list`, `serverless_stats`, `history`, `gateway_providers` |
| Billing | `client.billing` | `query_all_amount`, `query_bill_by_gpu`, `query_bill_by_service`, `query_bill_list` |
| Payment | `client.payment` | `records`, `statistics`, `get_record`, `billing_list`, `billing_query_all_amount` |
| ModelGallery | `client.model_gallery` | `page`, `providers`, `get_price` |
| ModelLab | `client.model_lab` | `list`, `create_session`, `stream` |

---

## CLI

Installing `wlt-platform` also installs a `wlt` command-line tool wrapping the SDK. Verify with `wlt --help`; you can also invoke it as `python -m wlt --help`.

> Most subcommands accept a `--json` flag to emit raw JSON output (useful for scripting).

### Authentication Sources

The CLI accepts an API Key from three sources (highest to lowest priority):

| Priority | Source | Notes |
|---|---|---|
| 1 | `--api-key` flag | Per-invocation override; the global `--api-key` flag does *not* persist (use `wlt login --api-key` to persist). |
| 2 | `WLT_API_KEY` environment variable | Convenient for CI / scripts. |
| 3 | `~/.wlt/config.json` | Written by `wlt login`; file permissions `0600`. |

Base URL resolution for the CLI follows the same precedence:

| Priority | Source |
|---|---|
| 1 | `--base-url` passed to `wlt login` (persisted) |
| 2 | `WLT_BASE_URL` environment variable |
| 3 | `base_url` in `~/.wlt/config.json` |
| 4 | Default `https://daily.sssxuntui.com` |

The config file uses a **dual-key convention** -- both `api_key` / `apiKey` and `base_url` / `baseUrl` are persisted, so the Python CLI and Node.js CLI share the same login state on the same machine.

### Common Commands

```bash
# Login (persists API Key + optional base URL to ~/.wlt/config.json)
wlt login --api-key sk-xxxxxxxxxxxxxxxxxxxx
wlt login --api-key sk-xxxxxxxxxxxxxxxxxxxx --base-url https://your.host

# Logout (removes local credentials)
wlt logout

# Show current user
wlt whoami

# Manage local config (~/.wlt/config.json)
wlt config set base_url https://your.host
wlt config set api_key sk-xxxxxxxxxxxxxxxxxxxx
wlt config get base_url
wlt config list                     # api_key shown masked

# Resource commands
wlt secret list [--page-num N] [--page-size N]
wlt secret detail <id>
wlt secret create --name <name>
wlt secret edit <id> --name <new-name>
# Note (cross-language divergence): Python CLI uses `--id <id>` while Node CLI
# uses a positional `<id>`. Both target the same backend endpoint.
wlt secret action --id <id> --status ENABLE|DISABLE|DELETE
# Note: Python CLI's `usage-by-model` currently exposes only <id>+pagination
# flags (the SDK method itself accepts start/end time -- Node CLI exposes
# --start/--end; Python CLI alignment is a known follow-up).
wlt secret usage-by-model <id> [--page-num N] [--page-size N]
wlt secret models
wlt secret providers

wlt apikey list [--page-num N] [--page-size N]
wlt apikey create --name <name> --provider <provider> --key <secret> [--key-id <id>]
wlt apikey update <id> --name <new-name>
wlt apikey delete <id>
wlt apikey change-status <id> --status 1|0
wlt apikey test-connect --provider <provider> --key <secret> [--key-id <id>]
wlt apikey providers

wlt usage list --start <date> --end <date> [--page-num N] [--page-size N]
wlt usage stats --start <date> --end <date>
wlt usage history [--page-num N] [--page-size N] [--start <date>] [--end <date>]
wlt usage providers

wlt billing summary --cycle YYYY-MM
wlt billing by-gpu --cycle YYYY-MM [--page-num N] [--page-size N]
wlt billing by-service --cycle YYYY-MM [--page-num N] [--page-size N]
wlt billing list --cycle YYYY-MM [--page-num N] [--page-size N]

wlt payment records [--page-num N] [--page-size N]
wlt payment statistics
wlt payment detail <transaction_no>
# Note: payment billing-list / billing-amount require full datetime
# "YYYY-MM-DD HH:MM:SS" (NOT bare "YYYY-MM-DD" -- backend rejects it).
# usage list / stats / history accept either form.
wlt payment billing-list  --start "2026-04-01 00:00:00" --end "2026-04-30 23:59:59" [--page-num N] [--page-size N]
wlt payment billing-amount --start "2026-04-01 00:00:00" --end "2026-04-30 23:59:59"

wlt model list [--page-num N] [--page-size N]
wlt model providers
wlt model price <model-name>

wlt lab list [--page-num N] [--page-size N]
wlt lab session
wlt lab chat --provider <provider> --model <name> --message "<text>" [--session-id <id>]
```

#### Streaming Chat — runnable example

`wlt lab chat` requires a real `provider` + `model` combination that is registered in the model registry. To discover what's available:

```bash
# 1. Find available gateway providers
wlt usage providers

# 2. List available models with their callable provider
wlt lab list --page-num 1 --page-size 5

# 3. Stream a real chat (daily endpoint via OpenRouter gateway)
wlt lab chat --provider openrouter --model openai/gpt-5.4-nano --message "Say hi in 5 words"
```

### CLI vs SDK surface

The CLI exposes a curated subset of the SDK's surface. Three things to know
when comparing CLI flags to SDK method parameters:

1. **Some flags re-map to differently-named SDK parameters.** The CLI uses
   the shorter, more user-friendly name; the SDK uses the backend's JSON name.
   - `wlt apikey create --name <n>` → SDK `client.api_keys.create(key_name=n, ...)`
   - `wlt apikey create --key <s>` → SDK `access_key_secret=s`
   - `wlt apikey create --key-id <id>` → SDK `access_key_id=id`
   - `wlt apikey test-connect --key <s> --key-id <id>` → same mapping as above
2. **Not every SDK parameter is exposed as a CLI flag.** Lower-traffic filters
   such as `status` / `expired_before_utc` on `secrets.list()`, `provider` /
   `timezone` on `api_keys.list()`, `tpm_limit` / `token_limit` on
   `api_keys.create()`, and `api_key` / `inputs` / `outputs` on
   `usage.serverless_list()` are SDK-only. Reach for the SDK when you need them.
3. **Cross-language divergences** (Python vs Node CLI):
   - `wlt secret action` — Python uses `--id <id>`, Node uses positional `<id>`
   - `wlt secret usage-by-model` — Node accepts `--start/--end`, Python does
     not currently (follow-up). The SDK method itself accepts both.

### Global Options

| Option | Description |
|---|---|
| `--help` | Show command help. |
| `--version` | Show CLI version. |
| `--api-key <key>` | Override API Key for the current invocation (not persisted). |
| `--base-url <url>` | Override base URL for the current invocation. |
| `--json` | Emit raw JSON output (default: human-readable). |

### Base URL Resolution

The CLI follows the four-level precedence documented in [Base URL Configuration](#base-url-configuration). When `--base-url` is omitted, the CLI falls back to `WLT_BASE_URL`, then `~/.wlt/config.json` `base_url`, then the default `https://daily.sssxuntui.com`.

## License

Apache-2.0
