Skip to content

Commit a9e645c

Browse files
authored
Merge pull request #5 from ofspectrum/sync-sdk-v1.1.6
Sync SDK v1.1.6 from neo
2 parents c3310f8 + 0b93bcd commit a9e645c

10 files changed

Lines changed: 528 additions & 36 deletions

File tree

‎AGENT_GUIDE.md‎

Lines changed: 211 additions & 25 deletions
Large diffs are not rendered by default.

‎CHANGELOG.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
## 1.1.6
6+
7+
- Added notebook media limits of 10 files, 100 MB per file, and 10 GB total.
8+
- Added SDK methods to list and create reusable AI authorization tags.
9+
- Normalized unlimited private notebook limits to `None` in public SDK token models.
10+
311
## 1.1.4
412

513
- Updated token documentation for Standard, Pro, and eligible Enterprise workflows.

‎README.md‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,43 @@ token = client.tokens.update(
8585
token_id="token-uuid",
8686
public_key=verification_key,
8787
)
88+
89+
# Upgrade an existing Standard token to Pro.
90+
# Token types can be upgraded, but not downgraded.
91+
token = client.tokens.update(
92+
token_id="token-uuid",
93+
token_type="pro",
94+
public_key=verification_key,
95+
)
96+
97+
# Configure how the token may be used by AI systems.
98+
token = client.tokens.update(
99+
token_id="token-uuid",
100+
ai_auth_enabled=True,
101+
ai_auth_access_type="direct_use", # or "premium_track"
102+
ai_auth_price=5,
103+
ai_auth_other_instructions="Attribution required",
104+
ai_auth_tags=["voice", "licensed"],
105+
)
88106
```
89107

108+
Passing `ai_auth_price=None` clears the price. Passing `ai_auth_tags=[]` removes all AI authorization tags from the token.
109+
110+
Reusable AI authorization tags can be listed or created separately:
111+
112+
```python
113+
tags = client.tokens.list_ai_auth_tags()
114+
voice_tag = client.tokens.create_ai_auth_tag("Voice Clone")
115+
116+
client.tokens.update(
117+
token_id="token-uuid",
118+
ai_auth_tags=[voice_tag.tag],
119+
)
120+
```
121+
122+
Creating a tag does not attach it to a token. Pass the selected tag names to
123+
`tokens.create()` or `tokens.update()` to associate them with a token.
124+
90125
Token deletion is not available via API. Tokens are consumable resources.
91126

92127
## Audio Watermarking
@@ -144,8 +179,9 @@ Notebook limits:
144179

145180
| Token Type | Public Notebooks | Private Notebooks |
146181
|------------|------------------|-------------------|
147-
| `standard` | 1 | 0 |
148-
| `pro` | 1 | 1 |
182+
| `standard` | 1 | 1 |
183+
| `pro` | 1 | Unlimited |
184+
| `enterprise` | 1 | Unlimited |
149185

150186
If the limit is reached, the SDK raises a `ValidationError` with a customer-facing message.
151187

@@ -173,6 +209,9 @@ client.notebooks.upload_media(
173209
notebooks = client.notebooks.list(token_id=token.id)
174210
```
175211

212+
Each notebook accepts up to 10 media files. Each file may be up to 100 MB, and
213+
the combined media size limit is 10 GB per notebook.
214+
176215
## Quota Checking
177216

178217
```python

‎ofspectrum/__init__.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
print(f"Remaining: {quota.remaining}/{quota.limit}")
3030
"""
3131

32-
__version__ = "1.1.5"
32+
__version__ = "1.1.6"
3333
__author__ = "OfSpectrum"
3434

3535
from .client import OfSpectrum, AsyncOfSpectrum
@@ -47,6 +47,7 @@
4747
)
4848
from .models import (
4949
Token,
50+
AiAuthTag,
5051
TokenCreateParams,
5152
TokenUpdateParams,
5253
Notebook,
@@ -77,6 +78,7 @@
7778
"NetworkError",
7879
# Models
7980
"Token",
81+
"AiAuthTag",
8082
"TokenCreateParams",
8183
"TokenUpdateParams",
8284
"Notebook",

‎ofspectrum/exceptions.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ def _raise_detail_error(message: str, status_code: int):
222222
if message == "Private notebook limit reached for this token":
223223
raise ValidationError(
224224
message=(
225-
"Private notebook limit reached for this token. Standard tokens do not support "
226-
"private notebooks; Pro tokens support one private notebook."
225+
"Private notebook limit reached for this token. Standard tokens support one "
226+
"private notebook; Pro tokens have no private notebook limit."
227227
),
228228
code="NotebookLimit",
229229
status_code=status_code or 400,

‎ofspectrum/models/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
OfSpectrum SDK Data Models
33
"""
44

5-
from .token import Token, TokenCreateParams, TokenUpdateParams
5+
from .token import AiAuthTag, Token, TokenCreateParams, TokenUpdateParams
66
from .notebook import Notebook, NotebookMedia, NotebookCreateParams
77
from .audio import EncodeResult, DecodeResult, StreamingEncodeResult
88
from .quota import Quota, QuotaList
99

1010
__all__ = [
1111
"Token",
12+
"AiAuthTag",
1213
"TokenCreateParams",
1314
"TokenUpdateParams",
1415
"Notebook",

‎ofspectrum/models/token.py‎

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,29 @@
33
"""
44

55
from dataclasses import dataclass, field
6-
from typing import Optional, Literal
7-
from datetime import datetime
6+
from typing import Any, List, Literal, Optional
7+
8+
9+
UNSET = object()
10+
11+
12+
@dataclass
13+
class AiAuthTag:
14+
"""Reusable AI authorization tag owned by the current account."""
15+
16+
id: str
17+
tag: str
18+
created_at: Optional[str] = None
19+
updated_at: Optional[str] = None
20+
21+
@classmethod
22+
def from_dict(cls, data: dict) -> "AiAuthTag":
23+
return cls(
24+
id=data["id"],
25+
tag=data.get("tag", ""),
26+
created_at=data.get("created_at"),
27+
updated_at=data.get("updated_at"),
28+
)
829

930

1031
@dataclass
@@ -16,6 +37,12 @@ class Token:
1637
token_type: Literal["standard", "pro", "enterprise"]
1738
public_key: Optional[int] = None
1839
enterprise_verification: bool = False
40+
max_private_notes: Optional[int] = None
41+
ai_auth_enabled: bool = False
42+
ai_auth_access_type: Optional[Literal["direct_use", "premium_track"]] = None
43+
ai_auth_price: Optional[float] = None
44+
ai_auth_other_instructions: Optional[str] = None
45+
ai_auth_tags: List[str] = field(default_factory=list)
1946
created_at: Optional[str] = None
2047
updated_at: Optional[str] = None
2148

@@ -28,6 +55,14 @@ def from_dict(cls, data: dict) -> "Token":
2855
token_type=data.get("token_type", "standard"),
2956
public_key=data.get("public_key"),
3057
enterprise_verification=data.get("enterprise_verification", False),
58+
max_private_notes=None
59+
if data.get("max_private_notes") is None or int(data.get("max_private_notes", 0)) < 0
60+
else int(data.get("max_private_notes", 0)),
61+
ai_auth_enabled=data.get("ai_auth_enabled", False),
62+
ai_auth_access_type=data.get("ai_auth_access_type") or None,
63+
ai_auth_price=data.get("ai_auth_price"),
64+
ai_auth_other_instructions=data.get("ai_auth_other_instructions") or None,
65+
ai_auth_tags=list(data.get("ai_auth_tags") or []),
3166
created_at=data.get("created_at"),
3267
updated_at=data.get("updated_at"),
3368
)
@@ -40,6 +75,11 @@ class TokenCreateParams:
4075
name: str
4176
token_type: Literal["standard", "pro"] = "standard"
4277
public_key: Optional[int] = None
78+
ai_auth_enabled: bool = False
79+
ai_auth_access_type: Optional[Literal["direct_use", "premium_track"]] = None
80+
ai_auth_price: Optional[float] = None
81+
ai_auth_other_instructions: Optional[str] = None
82+
ai_auth_tags: Optional[List[str]] = None
4383

4484
def to_dict(self) -> dict:
4585
"""Convert to API request dict"""
@@ -49,6 +89,16 @@ def to_dict(self) -> dict:
4989
}
5090
if self.public_key is not None:
5191
data["public_key"] = self.public_key
92+
if self.ai_auth_enabled:
93+
data["ai_auth_enabled"] = True
94+
if self.ai_auth_access_type is not None:
95+
data["ai_auth_access_type"] = self.ai_auth_access_type
96+
if self.ai_auth_price is not None:
97+
data["ai_auth_price"] = self.ai_auth_price
98+
if self.ai_auth_other_instructions is not None:
99+
data["ai_auth_other_instructions"] = self.ai_auth_other_instructions
100+
if self.ai_auth_tags is not None:
101+
data["ai_auth_tags"] = self.ai_auth_tags
52102
return data
53103

54104

@@ -58,7 +108,13 @@ class TokenUpdateParams:
58108

59109
name: Optional[str] = None
60110
public_key: Optional[int] = None
111+
token_type: Optional[Literal["standard", "pro", "enterprise"]] = None
61112
enterprise_verification: Optional[bool] = None
113+
ai_auth_enabled: Optional[bool] = None
114+
ai_auth_access_type: Optional[Literal["direct_use", "premium_track"]] = None
115+
ai_auth_price: Any = UNSET
116+
ai_auth_other_instructions: Optional[str] = None
117+
ai_auth_tags: Optional[List[str]] = None
62118

63119
def to_dict(self) -> dict:
64120
"""Convert to API request dict (only non-None fields)"""
@@ -67,6 +123,18 @@ def to_dict(self) -> dict:
67123
data["name"] = self.name
68124
if self.public_key is not None:
69125
data["public_key"] = self.public_key
126+
if self.token_type is not None:
127+
data["token_type"] = self.token_type
70128
if self.enterprise_verification is not None:
71129
data["enterprise_verification"] = self.enterprise_verification
130+
if self.ai_auth_enabled is not None:
131+
data["ai_auth_enabled"] = self.ai_auth_enabled
132+
if self.ai_auth_access_type is not None:
133+
data["ai_auth_access_type"] = self.ai_auth_access_type
134+
if self.ai_auth_price is not UNSET:
135+
data["ai_auth_price"] = self.ai_auth_price
136+
if self.ai_auth_other_instructions is not None:
137+
data["ai_auth_other_instructions"] = self.ai_auth_other_instructions
138+
if self.ai_auth_tags is not None:
139+
data["ai_auth_tags"] = self.ai_auth_tags
72140
return data

‎ofspectrum/resources/tokens.py‎

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,37 @@
22
Tokens resource for managing watermark tokens
33
"""
44

5-
from typing import List, Optional, Literal
5+
from typing import Any, List, Literal, Optional
66
from .base import BaseResource
7-
from ..models.token import Token, TokenCreateParams, TokenUpdateParams
7+
from ..models.token import AiAuthTag, UNSET, Token, TokenCreateParams, TokenUpdateParams
88
from ..exceptions import raise_for_error
99

1010

1111
class TokensResource(BaseResource):
1212
"""Resource for managing watermark tokens"""
1313

14+
def list_ai_auth_tags(self) -> List[AiAuthTag]:
15+
"""List reusable AI authorization tags for the current account."""
16+
response = self._get("/tokens/ai-auth-tags")
17+
data = response.json()
18+
raise_for_error(data, response.status_code)
19+
20+
tags_data = data if isinstance(data, list) else data.get("data", {}).get("tags", [])
21+
return [AiAuthTag.from_dict(tag) for tag in tags_data]
22+
23+
def create_ai_auth_tag(self, tag: str) -> AiAuthTag:
24+
"""Create or retrieve a reusable AI authorization tag."""
25+
normalized_tag = tag.strip()
26+
if not normalized_tag:
27+
raise ValueError("tag must not be empty")
28+
29+
response = self._post("/tokens/ai-auth-tags", json={"tag": normalized_tag})
30+
data = response.json()
31+
raise_for_error(data, response.status_code)
32+
33+
tag_data = data if isinstance(data, dict) and "id" in data else data.get("data", {})
34+
return AiAuthTag.from_dict(tag_data)
35+
1436
def list(self) -> List[Token]:
1537
"""
1638
List all tokens for the current user.
@@ -58,6 +80,11 @@ def create(
5880
name: str,
5981
token_type: Literal["standard", "pro"] = "standard",
6082
public_key: Optional[int] = None,
83+
ai_auth_enabled: bool = False,
84+
ai_auth_access_type: Optional[Literal["direct_use", "premium_track"]] = None,
85+
ai_auth_price: Optional[float] = None,
86+
ai_auth_other_instructions: Optional[str] = None,
87+
ai_auth_tags: Optional[List[str]] = None,
6188
) -> Token:
6289
"""
6390
Create a new watermark token.
@@ -67,6 +94,11 @@ def create(
6794
token_type: "standard" (default) or "pro"
6895
public_key: Verification key when your token workflow requires one.
6996
Defaults to 258 for pro tokens if not provided.
97+
ai_auth_enabled: Whether AI authorization is enabled
98+
ai_auth_access_type: "direct_use" or "premium_track" (optional)
99+
ai_auth_price: AI authorization price; must be at least 1 when set
100+
ai_auth_other_instructions: Additional AI authorization instructions
101+
ai_auth_tags: Searchable AI authorization tags
70102
71103
Returns:
72104
Newly created Token object
@@ -95,6 +127,11 @@ def create(
95127
name=name,
96128
token_type=token_type,
97129
public_key=public_key,
130+
ai_auth_enabled=ai_auth_enabled,
131+
ai_auth_access_type=ai_auth_access_type,
132+
ai_auth_price=ai_auth_price,
133+
ai_auth_other_instructions=ai_auth_other_instructions,
134+
ai_auth_tags=ai_auth_tags,
98135
)
99136

100137
response = self._post("/tokens/", json=params.to_dict())
@@ -111,7 +148,13 @@ def update(
111148
token_id: str,
112149
name: Optional[str] = None,
113150
public_key: Optional[int] = None,
151+
token_type: Optional[Literal["standard", "pro", "enterprise"]] = None,
114152
enterprise_verification: Optional[bool] = None,
153+
ai_auth_enabled: Optional[bool] = None,
154+
ai_auth_access_type: Optional[Literal["direct_use", "premium_track"]] = None,
155+
ai_auth_price: Any = UNSET,
156+
ai_auth_other_instructions: Optional[str] = None,
157+
ai_auth_tags: Optional[List[str]] = None,
115158
) -> Token:
116159
"""
117160
Update an existing token.
@@ -120,15 +163,27 @@ def update(
120163
token_id: The token UUID
121164
name: New name (optional)
122165
public_key: New verification key when your token workflow supports it (optional)
166+
token_type: New token type. The API permits upgrades but rejects downgrades.
123167
enterprise_verification: Enterprise-only verification setting for eligible accounts (optional)
168+
ai_auth_enabled: Whether AI authorization is enabled
169+
ai_auth_access_type: "direct_use" or "premium_track" (optional)
170+
ai_auth_price: AI authorization price, or None to clear it
171+
ai_auth_other_instructions: Additional AI authorization instructions
172+
ai_auth_tags: Replacement list of AI authorization tags
124173
125174
Returns:
126175
Updated Token object
127176
"""
128177
params = TokenUpdateParams(
129178
name=name,
130179
public_key=public_key,
180+
token_type=token_type,
131181
enterprise_verification=enterprise_verification,
182+
ai_auth_enabled=ai_auth_enabled,
183+
ai_auth_access_type=ai_auth_access_type,
184+
ai_auth_price=ai_auth_price,
185+
ai_auth_other_instructions=ai_auth_other_instructions,
186+
ai_auth_tags=ai_auth_tags,
132187
)
133188

134189
update_data = params.to_dict()

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ofspectrum"
7-
version = "1.1.5"
7+
version = "1.1.6"
88
description = "OfSpectrum Audio Watermarking SDK"
99
readme = "README.md"
1010
license = {text = "MIT"}

0 commit comments

Comments
 (0)