From a33ed30a94cd56737747e3492b5bddf4ea8a8183 Mon Sep 17 00:00:00 2001
From: "onepin-pipeline-bot[bot]"
<290953255+onepin-pipeline-bot[bot]@users.noreply.github.com>
Date: Thu, 18 Jun 2026 02:07:53 +0000
Subject: [PATCH] feat: sync SDK to OnePin API v0.38.12
---
.spec-sha | 2 +-
src/onepin/.fern/metadata.json | 14 +-
src/onepin/__init__.py | 57 +-
src/onepin/client.py | 19 +
src/onepin/core/client_wrapper.py | 2 +-
src/onepin/nodes/client.py | 114 +++
src/onepin/nodes/raw_client.py | 172 ++++
src/onepin/providers/__init__.py | 4 +
src/onepin/providers/client.py | 512 ++++++++++++
src/onepin/providers/raw_client.py | 777 ++++++++++++++++++
src/onepin/reference.md | 533 ++++++++++++
src/onepin/types/__init__.py | 39 +-
...ounted_list_response_catalog_voice_out.py} | 15 +-
src/onepin/types/api_key_scope.py | 1 +
.../api_list_response_catalog_model_out.py | 24 +
.../api_list_response_catalog_provider_out.py | 24 +
.../types/api_response_catalog_model_out.py | 22 +
.../api_response_catalog_provider_out.py | 22 +
src/onepin/types/catalog_link.py | 27 +
src/onepin/types/catalog_model_out.py | 33 +
src/onepin/types/catalog_provider_out.py | 31 +
src/onepin/types/catalog_voice_out.py | 36 +
src/onepin/types/node_type.py | 1 +
.../types/workflow_run_data_card_out.py | 4 +-
.../types/workflow_run_data_validation_out.py | 35 +
...orkflow_run_data_validation_out_status.py} | 2 +-
26 files changed, 2487 insertions(+), 35 deletions(-)
create mode 100644 src/onepin/providers/__init__.py
create mode 100644 src/onepin/providers/client.py
create mode 100644 src/onepin/providers/raw_client.py
rename src/onepin/types/{workflow_run_data_scores_out.py => api_counted_list_response_catalog_voice_out.py} (52%)
create mode 100644 src/onepin/types/api_list_response_catalog_model_out.py
create mode 100644 src/onepin/types/api_list_response_catalog_provider_out.py
create mode 100644 src/onepin/types/api_response_catalog_model_out.py
create mode 100644 src/onepin/types/api_response_catalog_provider_out.py
create mode 100644 src/onepin/types/catalog_link.py
create mode 100644 src/onepin/types/catalog_model_out.py
create mode 100644 src/onepin/types/catalog_provider_out.py
create mode 100644 src/onepin/types/catalog_voice_out.py
create mode 100644 src/onepin/types/workflow_run_data_validation_out.py
rename src/onepin/types/{workflow_run_data_scores_out_accuracy_status.py => workflow_run_data_validation_out_status.py} (75%)
diff --git a/.spec-sha b/.spec-sha
index 8d33425..68677bc 100644
--- a/.spec-sha
+++ b/.spec-sha
@@ -1 +1 @@
-9fe47587946f7ab984ac1dd701c85c15c368a67c
+22d7f164e18c741e9d6e2cba0f280a3a3af4d153
diff --git a/src/onepin/.fern/metadata.json b/src/onepin/.fern/metadata.json
index c57a2d6..8c61ab7 100644
--- a/src/onepin/.fern/metadata.json
+++ b/src/onepin/.fern/metadata.json
@@ -5,9 +5,19 @@
"generatorConfig": {
"client_class_name": "OnePinClient",
"package_name": "onepin",
- "flat_layout": false
+ "flat_layout": false,
+ "additional_init_exports": [
+ {
+ "from": "_version_gate",
+ "imports": [
+ "make_client",
+ "make_async_client",
+ "OnePinUpgradeRequiredError"
+ ]
+ }
+ ]
},
- "originGitCommit": "2bb9115ea1292fce9ca3019bd7c90ba226cc58b6",
+ "originGitCommit": "e61a07d289073104a086ed6aec6161a6169f829a",
"originGitCommitIsDirty": false,
"invokedBy": "ci",
"ciProvider": "github"
diff --git a/src/onepin/__init__.py b/src/onepin/__init__.py
index 3923001..21a972c 100644
--- a/src/onepin/__init__.py
+++ b/src/onepin/__init__.py
@@ -8,6 +8,7 @@
if typing.TYPE_CHECKING:
from .types import (
ApiCountedListResponseApiKeyOut,
+ ApiCountedListResponseCatalogVoiceOut,
ApiCountedListResponseVoiceOut,
ApiCountedListResponseWorkflowListItem,
ApiCountedListResponseWorkflowRunListItem,
@@ -19,6 +20,8 @@
ApiKeyOut,
ApiKeyRotateOut,
ApiKeyScope,
+ ApiListResponseCatalogModelOut,
+ ApiListResponseCatalogProviderOut,
ApiListResponseCustomerPlanResponse,
ApiListResponseDictionaryOut,
ApiListResponseNodePortsOut,
@@ -33,6 +36,8 @@
ApiResponseApiKeyRotateOut,
ApiResponseAuthWhoamiOut,
ApiResponseBalanceResponse,
+ ApiResponseCatalogModelOut,
+ ApiResponseCatalogProviderOut,
ApiResponseCheckoutResponse,
ApiResponseCustomerPlanChangePreviewResponse,
ApiResponseCustomerSubscriptionResponse,
@@ -73,6 +78,10 @@
AuthWhoamiOut,
AuthWhoamiOutAuthKind,
BalanceResponse,
+ CatalogLink,
+ CatalogModelOut,
+ CatalogProviderOut,
+ CatalogVoiceOut,
CheckoutResponse,
CountedPaginationMeta,
CustomerPlanChangePreviewResponse,
@@ -180,8 +189,8 @@
WorkflowRunDataRowOut,
WorkflowRunDataRowOutAutoCorrectedStatus,
WorkflowRunDataRowOutScriptStatus,
- WorkflowRunDataScoresOut,
- WorkflowRunDataScoresOutAccuracyStatus,
+ WorkflowRunDataValidationOut,
+ WorkflowRunDataValidationOutStatus,
WorkflowRunDataVoiceOut,
WorkflowRunDataVoiceOutStatus,
WorkflowRunDetailOut,
@@ -222,6 +231,7 @@
health,
nodes,
provider_keys,
+ providers,
templates,
uploads,
usage,
@@ -235,6 +245,7 @@
workspaces,
)
from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient
+ from ._version_gate import OnePinUpgradeRequiredError, make_async_client, make_client
from .client import AsyncOnePinClient, OnePinClient
from .dictionary import (
ListDictionaryEntriesApiV1DictionaryGetRequestLanguage,
@@ -265,6 +276,7 @@
from .workflows import ListWorkflowsRequestOrderItem, ListWorkflowsRequestSortItem
_dynamic_imports: typing.Dict[str, str] = {
"ApiCountedListResponseApiKeyOut": ".types",
+ "ApiCountedListResponseCatalogVoiceOut": ".types",
"ApiCountedListResponseVoiceOut": ".types",
"ApiCountedListResponseWorkflowListItem": ".types",
"ApiCountedListResponseWorkflowRunListItem": ".types",
@@ -276,6 +288,8 @@
"ApiKeyOut": ".types",
"ApiKeyRotateOut": ".types",
"ApiKeyScope": ".types",
+ "ApiListResponseCatalogModelOut": ".types",
+ "ApiListResponseCatalogProviderOut": ".types",
"ApiListResponseCustomerPlanResponse": ".types",
"ApiListResponseDictionaryOut": ".types",
"ApiListResponseNodePortsOut": ".types",
@@ -290,6 +304,8 @@
"ApiResponseApiKeyRotateOut": ".types",
"ApiResponseAuthWhoamiOut": ".types",
"ApiResponseBalanceResponse": ".types",
+ "ApiResponseCatalogModelOut": ".types",
+ "ApiResponseCatalogProviderOut": ".types",
"ApiResponseCheckoutResponse": ".types",
"ApiResponseCustomerPlanChangePreviewResponse": ".types",
"ApiResponseCustomerSubscriptionResponse": ".types",
@@ -331,6 +347,10 @@
"AuthWhoamiOut": ".types",
"AuthWhoamiOutAuthKind": ".types",
"BalanceResponse": ".types",
+ "CatalogLink": ".types",
+ "CatalogModelOut": ".types",
+ "CatalogProviderOut": ".types",
+ "CatalogVoiceOut": ".types",
"CheckoutResponse": ".types",
"ConflictError": ".errors",
"CountedPaginationMeta": ".types",
@@ -385,6 +405,7 @@
"NumericOption": ".types",
"OnePinClient": ".client",
"OnePinClientEnvironment": ".environment",
+ "OnePinUpgradeRequiredError": "._version_gate",
"PaginationMeta": ".types",
"PaymentMethodResponse": ".types",
"PlanDetails": ".types",
@@ -467,8 +488,8 @@
"WorkflowRunDataRowOut": ".types",
"WorkflowRunDataRowOutAutoCorrectedStatus": ".types",
"WorkflowRunDataRowOutScriptStatus": ".types",
- "WorkflowRunDataScoresOut": ".types",
- "WorkflowRunDataScoresOutAccuracyStatus": ".types",
+ "WorkflowRunDataValidationOut": ".types",
+ "WorkflowRunDataValidationOutStatus": ".types",
"WorkflowRunDataVoiceOut": ".types",
"WorkflowRunDataVoiceOutStatus": ".types",
"WorkflowRunDetailOut": ".types",
@@ -504,8 +525,11 @@
"billing": ".billing",
"dictionary": ".dictionary",
"health": ".health",
+ "make_async_client": "._version_gate",
+ "make_client": "._version_gate",
"nodes": ".nodes",
"provider_keys": ".provider_keys",
+ "providers": ".providers",
"templates": ".templates",
"uploads": ".uploads",
"usage": ".usage",
@@ -517,11 +541,6 @@
"workspace_aggregates": ".workspace_aggregates",
"workspace_members": ".workspace_members",
"workspaces": ".workspaces",
- # Hand-rolled version gate, re-exported here. Also declared in
- # fern/generators.yml `additional_init_exports`, so `fern generate` reproduces these.
- "OnePinUpgradeRequiredError": "._version_gate",
- "make_async_client": "._version_gate",
- "make_client": "._version_gate",
}
@@ -548,6 +567,7 @@ def __dir__():
__all__ = [
"ApiCountedListResponseApiKeyOut",
+ "ApiCountedListResponseCatalogVoiceOut",
"ApiCountedListResponseVoiceOut",
"ApiCountedListResponseWorkflowListItem",
"ApiCountedListResponseWorkflowRunListItem",
@@ -559,6 +579,8 @@ def __dir__():
"ApiKeyOut",
"ApiKeyRotateOut",
"ApiKeyScope",
+ "ApiListResponseCatalogModelOut",
+ "ApiListResponseCatalogProviderOut",
"ApiListResponseCustomerPlanResponse",
"ApiListResponseDictionaryOut",
"ApiListResponseNodePortsOut",
@@ -573,6 +595,8 @@ def __dir__():
"ApiResponseApiKeyRotateOut",
"ApiResponseAuthWhoamiOut",
"ApiResponseBalanceResponse",
+ "ApiResponseCatalogModelOut",
+ "ApiResponseCatalogProviderOut",
"ApiResponseCheckoutResponse",
"ApiResponseCustomerPlanChangePreviewResponse",
"ApiResponseCustomerSubscriptionResponse",
@@ -614,6 +638,10 @@ def __dir__():
"AuthWhoamiOut",
"AuthWhoamiOutAuthKind",
"BalanceResponse",
+ "CatalogLink",
+ "CatalogModelOut",
+ "CatalogProviderOut",
+ "CatalogVoiceOut",
"CheckoutResponse",
"ConflictError",
"CountedPaginationMeta",
@@ -668,6 +696,7 @@ def __dir__():
"NumericOption",
"OnePinClient",
"OnePinClientEnvironment",
+ "OnePinUpgradeRequiredError",
"PaginationMeta",
"PaymentMethodResponse",
"PlanDetails",
@@ -750,8 +779,8 @@ def __dir__():
"WorkflowRunDataRowOut",
"WorkflowRunDataRowOutAutoCorrectedStatus",
"WorkflowRunDataRowOutScriptStatus",
- "WorkflowRunDataScoresOut",
- "WorkflowRunDataScoresOutAccuracyStatus",
+ "WorkflowRunDataValidationOut",
+ "WorkflowRunDataValidationOutStatus",
"WorkflowRunDataVoiceOut",
"WorkflowRunDataVoiceOutStatus",
"WorkflowRunDetailOut",
@@ -787,8 +816,11 @@ def __dir__():
"billing",
"dictionary",
"health",
+ "make_async_client",
+ "make_client",
"nodes",
"provider_keys",
+ "providers",
"templates",
"uploads",
"usage",
@@ -800,7 +832,4 @@ def __dir__():
"workspace_aggregates",
"workspace_members",
"workspaces",
- "OnePinUpgradeRequiredError",
- "make_async_client",
- "make_client",
]
diff --git a/src/onepin/client.py b/src/onepin/client.py
index e192699..9fcc96a 100644
--- a/src/onepin/client.py
+++ b/src/onepin/client.py
@@ -17,6 +17,7 @@
from .health.client import AsyncHealthClient, HealthClient
from .nodes.client import AsyncNodesClient, NodesClient
from .provider_keys.client import AsyncProviderKeysClient, ProviderKeysClient
+ from .providers.client import AsyncProvidersClient, ProvidersClient
from .templates.client import AsyncTemplatesClient, TemplatesClient
from .uploads.client import AsyncUploadsClient, UploadsClient
from .usage.client import AsyncUsageClient, UsageClient
@@ -113,6 +114,7 @@ def __init__(
self._dictionary: typing.Optional[DictionaryClient] = None
self._nodes: typing.Optional[NodesClient] = None
self._provider_keys: typing.Optional[ProviderKeysClient] = None
+ self._providers: typing.Optional[ProvidersClient] = None
self._templates: typing.Optional[TemplatesClient] = None
self._voices: typing.Optional[VoicesClient] = None
self._workspace: typing.Optional[WorkspaceClient] = None
@@ -181,6 +183,14 @@ def provider_keys(self):
self._provider_keys = ProviderKeysClient(client_wrapper=self._client_wrapper)
return self._provider_keys
+ @property
+ def providers(self):
+ if self._providers is None:
+ from .providers.client import ProvidersClient # noqa: E402
+
+ self._providers = ProvidersClient(client_wrapper=self._client_wrapper)
+ return self._providers
+
@property
def templates(self):
if self._templates is None:
@@ -374,6 +384,7 @@ def __init__(
self._dictionary: typing.Optional[AsyncDictionaryClient] = None
self._nodes: typing.Optional[AsyncNodesClient] = None
self._provider_keys: typing.Optional[AsyncProviderKeysClient] = None
+ self._providers: typing.Optional[AsyncProvidersClient] = None
self._templates: typing.Optional[AsyncTemplatesClient] = None
self._voices: typing.Optional[AsyncVoicesClient] = None
self._workspace: typing.Optional[AsyncWorkspaceClient] = None
@@ -442,6 +453,14 @@ def provider_keys(self):
self._provider_keys = AsyncProviderKeysClient(client_wrapper=self._client_wrapper)
return self._provider_keys
+ @property
+ def providers(self):
+ if self._providers is None:
+ from .providers.client import AsyncProvidersClient # noqa: E402
+
+ self._providers = AsyncProvidersClient(client_wrapper=self._client_wrapper)
+ return self._providers
+
@property
def templates(self):
if self._templates is None:
diff --git a/src/onepin/core/client_wrapper.py b/src/onepin/core/client_wrapper.py
index 7e6af11..fb6bf54 100644
--- a/src/onepin/core/client_wrapper.py
+++ b/src/onepin/core/client_wrapper.py
@@ -29,7 +29,7 @@ def get_headers(self) -> typing.Dict[str, str]:
import platform
headers: typing.Dict[str, str] = {
- "User-Agent": "onepin/0.5.1",
+ "User-Agent": "onepin/0.6.1",
"X-Fern-Language": "Python",
"X-Fern-Runtime": f"python/{platform.python_version()}",
"X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
diff --git a/src/onepin/nodes/client.py b/src/onepin/nodes/client.py
index f54f97d..a3ec2c1 100644
--- a/src/onepin/nodes/client.py
+++ b/src/onepin/nodes/client.py
@@ -60,6 +60,12 @@ def get_node_detail(
"""
Return full node definition + runtime options for the canvas node-config UI.
+ **Deprecated (POD-612):** this version inlines the model catalog as
+ `options.models_by_provider`. Use `GET /api/v2/nodes/{node_type}`, which
+ replaces the inline tree with a `providers` HATEOAS href to the standalone
+ catalog `/api/v1/providers`. This endpoint is kept for one release while the
+ FE migrates, then removed.
+
Unlike `GET /nodes` (which returns only port schemas), this endpoint returns the
actual runtime values a user picks: available target languages (from settings),
the TTS model catalog grouped by provider, and a HATEOAS link to the workspace-
@@ -95,6 +101,53 @@ def get_node_detail(
)
return _response.data
+ def get_node_detail_v2(
+ self,
+ node_type: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiResponseNodeDetailOut:
+ """
+ Return full node definition + runtime options (v2 — HATEOAS catalog href).
+
+ POD-612: replaces the deprecated v1 ``options.models_by_provider`` inline tree
+ with a ``providers`` HATEOAS href to the standalone catalog
+ ``/api/v1/providers``. The FE follows that href to fetch each model's
+ ``config_schema`` lazily. The ``voices`` href (with its provider/model/language
+ filter enums) is unchanged, so the voice picker never needs the catalog call.
+ Requires ``X-Workspace-Id`` for a uniform FE contract.
+
+ Parameters
+ ----------
+ node_type : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiResponseNodeDetailOut
+ Successful Response
+
+ Examples
+ --------
+ from onepin import OnePinClient
+
+ client = OnePinClient(
+ token="YOUR_TOKEN",
+ )
+ client.nodes.get_node_detail_v2(
+ node_type="node_type",
+ )
+ """
+ _response = self._raw_client.get_node_detail_v2(
+ node_type, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
class AsyncNodesClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
@@ -157,6 +210,12 @@ async def get_node_detail(
"""
Return full node definition + runtime options for the canvas node-config UI.
+ **Deprecated (POD-612):** this version inlines the model catalog as
+ `options.models_by_provider`. Use `GET /api/v2/nodes/{node_type}`, which
+ replaces the inline tree with a `providers` HATEOAS href to the standalone
+ catalog `/api/v1/providers`. This endpoint is kept for one release while the
+ FE migrates, then removed.
+
Unlike `GET /nodes` (which returns only port schemas), this endpoint returns the
actual runtime values a user picks: available target languages (from settings),
the TTS model catalog grouped by provider, and a HATEOAS link to the workspace-
@@ -199,3 +258,58 @@ async def main() -> None:
node_type, workspace_id=workspace_id, request_options=request_options
)
return _response.data
+
+ async def get_node_detail_v2(
+ self,
+ node_type: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiResponseNodeDetailOut:
+ """
+ Return full node definition + runtime options (v2 — HATEOAS catalog href).
+
+ POD-612: replaces the deprecated v1 ``options.models_by_provider`` inline tree
+ with a ``providers`` HATEOAS href to the standalone catalog
+ ``/api/v1/providers``. The FE follows that href to fetch each model's
+ ``config_schema`` lazily. The ``voices`` href (with its provider/model/language
+ filter enums) is unchanged, so the voice picker never needs the catalog call.
+ Requires ``X-Workspace-Id`` for a uniform FE contract.
+
+ Parameters
+ ----------
+ node_type : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiResponseNodeDetailOut
+ Successful Response
+
+ Examples
+ --------
+ import asyncio
+
+ from onepin import AsyncOnePinClient
+
+ client = AsyncOnePinClient(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.nodes.get_node_detail_v2(
+ node_type="node_type",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_node_detail_v2(
+ node_type, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
diff --git a/src/onepin/nodes/raw_client.py b/src/onepin/nodes/raw_client.py
index 7bfb174..8f1ff8f 100644
--- a/src/onepin/nodes/raw_client.py
+++ b/src/onepin/nodes/raw_client.py
@@ -72,6 +72,12 @@ def get_node_detail(
"""
Return full node definition + runtime options for the canvas node-config UI.
+ **Deprecated (POD-612):** this version inlines the model catalog as
+ `options.models_by_provider`. Use `GET /api/v2/nodes/{node_type}`, which
+ replaces the inline tree with a `providers` HATEOAS href to the standalone
+ catalog `/api/v1/providers`. This endpoint is kept for one release while the
+ FE migrates, then removed.
+
Unlike `GET /nodes` (which returns only port schemas), this endpoint returns the
actual runtime values a user picks: available target languages (from settings),
the TTS model catalog grouped by provider, and a HATEOAS link to the workspace-
@@ -140,6 +146,86 @@ def get_node_detail(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+ def get_node_detail_v2(
+ self,
+ node_type: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ApiResponseNodeDetailOut]:
+ """
+ Return full node definition + runtime options (v2 — HATEOAS catalog href).
+
+ POD-612: replaces the deprecated v1 ``options.models_by_provider`` inline tree
+ with a ``providers`` HATEOAS href to the standalone catalog
+ ``/api/v1/providers``. The FE follows that href to fetch each model's
+ ``config_schema`` lazily. The ``voices`` href (with its provider/model/language
+ filter enums) is unchanged, so the voice picker never needs the catalog call.
+ Requires ``X-Workspace-Id`` for a uniform FE contract.
+
+ Parameters
+ ----------
+ node_type : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ApiResponseNodeDetailOut]
+ Successful Response
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v2/nodes/{encode_path_param(node_type)}",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiResponseNodeDetailOut,
+ parse_obj_as(
+ type_=ApiResponseNodeDetailOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
class AsyncRawNodesClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
@@ -195,6 +281,12 @@ async def get_node_detail(
"""
Return full node definition + runtime options for the canvas node-config UI.
+ **Deprecated (POD-612):** this version inlines the model catalog as
+ `options.models_by_provider`. Use `GET /api/v2/nodes/{node_type}`, which
+ replaces the inline tree with a `providers` HATEOAS href to the standalone
+ catalog `/api/v1/providers`. This endpoint is kept for one release while the
+ FE migrates, then removed.
+
Unlike `GET /nodes` (which returns only port schemas), this endpoint returns the
actual runtime values a user picks: available target languages (from settings),
the TTS model catalog grouped by provider, and a HATEOAS link to the workspace-
@@ -262,3 +354,83 @@ async def get_node_detail(
status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get_node_detail_v2(
+ self,
+ node_type: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ApiResponseNodeDetailOut]:
+ """
+ Return full node definition + runtime options (v2 — HATEOAS catalog href).
+
+ POD-612: replaces the deprecated v1 ``options.models_by_provider`` inline tree
+ with a ``providers`` HATEOAS href to the standalone catalog
+ ``/api/v1/providers``. The FE follows that href to fetch each model's
+ ``config_schema`` lazily. The ``voices`` href (with its provider/model/language
+ filter enums) is unchanged, so the voice picker never needs the catalog call.
+ Requires ``X-Workspace-Id`` for a uniform FE contract.
+
+ Parameters
+ ----------
+ node_type : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ApiResponseNodeDetailOut]
+ Successful Response
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v2/nodes/{encode_path_param(node_type)}",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiResponseNodeDetailOut,
+ parse_obj_as(
+ type_=ApiResponseNodeDetailOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/onepin/providers/__init__.py b/src/onepin/providers/__init__.py
new file mode 100644
index 0000000..5cde020
--- /dev/null
+++ b/src/onepin/providers/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/src/onepin/providers/client.py b/src/onepin/providers/client.py
new file mode 100644
index 0000000..1a1a7e2
--- /dev/null
+++ b/src/onepin/providers/client.py
@@ -0,0 +1,512 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.request_options import RequestOptions
+from ..types.api_counted_list_response_catalog_voice_out import ApiCountedListResponseCatalogVoiceOut
+from ..types.api_list_response_catalog_model_out import ApiListResponseCatalogModelOut
+from ..types.api_list_response_catalog_provider_out import ApiListResponseCatalogProviderOut
+from ..types.api_response_catalog_model_out import ApiResponseCatalogModelOut
+from ..types.api_response_catalog_provider_out import ApiResponseCatalogProviderOut
+from .raw_client import AsyncRawProvidersClient, RawProvidersClient
+
+
+class ProvidersClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawProvidersClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawProvidersClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawProvidersClient
+ """
+ return self._raw_client
+
+ def list_catalog_providers(
+ self, *, workspace_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
+ ) -> ApiListResponseCatalogProviderOut:
+ """
+ List TTS providers in the public catalog.
+
+ Returns the processing (TTS) provider catalog with a per-provider model count
+ and a HATEOAS link to each provider's models. Lean / customer-safe — cost,
+ credentials, and base URLs are never exposed. Requires `X-Workspace-Id` (or a
+ workspace-bound API key with the `catalog:read` scope), matching `/voices`.
+
+ Parameters
+ ----------
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiListResponseCatalogProviderOut
+ Successful Response
+
+ Examples
+ --------
+ from onepin import OnePinClient
+
+ client = OnePinClient(
+ token="YOUR_TOKEN",
+ )
+ client.providers.list_catalog_providers()
+ """
+ _response = self._raw_client.list_catalog_providers(workspace_id=workspace_id, request_options=request_options)
+ return _response.data
+
+ def get_catalog_provider(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiResponseCatalogProviderOut:
+ """
+ Get a single TTS provider by canonical name (e.g. `cartesia`).
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiResponseCatalogProviderOut
+ Successful Response
+
+ Examples
+ --------
+ from onepin import OnePinClient
+
+ client = OnePinClient(
+ token="YOUR_TOKEN",
+ )
+ client.providers.get_catalog_provider(
+ provider="provider",
+ )
+ """
+ _response = self._raw_client.get_catalog_provider(
+ provider, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+ def list_catalog_provider_models(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiListResponseCatalogModelOut:
+ """
+ List a provider's TTS models, each with its `config_schema` and live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiListResponseCatalogModelOut
+ Successful Response
+
+ Examples
+ --------
+ from onepin import OnePinClient
+
+ client = OnePinClient(
+ token="YOUR_TOKEN",
+ )
+ client.providers.list_catalog_provider_models(
+ provider="provider",
+ )
+ """
+ _response = self._raw_client.list_catalog_provider_models(
+ provider, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+ def list_catalog_provider_model_voices(
+ self,
+ provider: str,
+ model: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiCountedListResponseCatalogVoiceOut:
+ """
+ List platform voices catalogued under an exact `(provider, model)`.
+
+ Lean voice shape with a presigned `preview_url`. Platform catalog voices only
+ (System workspace); model-less voices are excluded. Paginated via `offset` /
+ `limit`. The flat `/voices?provider=&model=` endpoint remains for the picker.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ offset : typing.Optional[int]
+
+ limit : typing.Optional[int]
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiCountedListResponseCatalogVoiceOut
+ Successful Response
+
+ Examples
+ --------
+ from onepin import OnePinClient
+
+ client = OnePinClient(
+ token="YOUR_TOKEN",
+ )
+ client.providers.list_catalog_provider_model_voices(
+ provider="provider",
+ model="model",
+ )
+ """
+ _response = self._raw_client.list_catalog_provider_model_voices(
+ provider, model, offset=offset, limit=limit, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+ def get_catalog_provider_model(
+ self,
+ provider: str,
+ model: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiResponseCatalogModelOut:
+ """
+ Get a single TTS model — `config_schema` + live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiResponseCatalogModelOut
+ Successful Response
+
+ Examples
+ --------
+ from onepin import OnePinClient
+
+ client = OnePinClient(
+ token="YOUR_TOKEN",
+ )
+ client.providers.get_catalog_provider_model(
+ provider="provider",
+ model="model",
+ )
+ """
+ _response = self._raw_client.get_catalog_provider_model(
+ provider, model, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+
+class AsyncProvidersClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawProvidersClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawProvidersClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawProvidersClient
+ """
+ return self._raw_client
+
+ async def list_catalog_providers(
+ self, *, workspace_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
+ ) -> ApiListResponseCatalogProviderOut:
+ """
+ List TTS providers in the public catalog.
+
+ Returns the processing (TTS) provider catalog with a per-provider model count
+ and a HATEOAS link to each provider's models. Lean / customer-safe — cost,
+ credentials, and base URLs are never exposed. Requires `X-Workspace-Id` (or a
+ workspace-bound API key with the `catalog:read` scope), matching `/voices`.
+
+ Parameters
+ ----------
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiListResponseCatalogProviderOut
+ Successful Response
+
+ Examples
+ --------
+ import asyncio
+
+ from onepin import AsyncOnePinClient
+
+ client = AsyncOnePinClient(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.providers.list_catalog_providers()
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list_catalog_providers(
+ workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+ async def get_catalog_provider(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiResponseCatalogProviderOut:
+ """
+ Get a single TTS provider by canonical name (e.g. `cartesia`).
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiResponseCatalogProviderOut
+ Successful Response
+
+ Examples
+ --------
+ import asyncio
+
+ from onepin import AsyncOnePinClient
+
+ client = AsyncOnePinClient(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.providers.get_catalog_provider(
+ provider="provider",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_catalog_provider(
+ provider, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+ async def list_catalog_provider_models(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiListResponseCatalogModelOut:
+ """
+ List a provider's TTS models, each with its `config_schema` and live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiListResponseCatalogModelOut
+ Successful Response
+
+ Examples
+ --------
+ import asyncio
+
+ from onepin import AsyncOnePinClient
+
+ client = AsyncOnePinClient(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.providers.list_catalog_provider_models(
+ provider="provider",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list_catalog_provider_models(
+ provider, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+ async def list_catalog_provider_model_voices(
+ self,
+ provider: str,
+ model: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiCountedListResponseCatalogVoiceOut:
+ """
+ List platform voices catalogued under an exact `(provider, model)`.
+
+ Lean voice shape with a presigned `preview_url`. Platform catalog voices only
+ (System workspace); model-less voices are excluded. Paginated via `offset` /
+ `limit`. The flat `/voices?provider=&model=` endpoint remains for the picker.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ offset : typing.Optional[int]
+
+ limit : typing.Optional[int]
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiCountedListResponseCatalogVoiceOut
+ Successful Response
+
+ Examples
+ --------
+ import asyncio
+
+ from onepin import AsyncOnePinClient
+
+ client = AsyncOnePinClient(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.providers.list_catalog_provider_model_voices(
+ provider="provider",
+ model="model",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list_catalog_provider_model_voices(
+ provider, model, offset=offset, limit=limit, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
+
+ async def get_catalog_provider_model(
+ self,
+ provider: str,
+ model: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ApiResponseCatalogModelOut:
+ """
+ Get a single TTS model — `config_schema` + live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ApiResponseCatalogModelOut
+ Successful Response
+
+ Examples
+ --------
+ import asyncio
+
+ from onepin import AsyncOnePinClient
+
+ client = AsyncOnePinClient(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.providers.get_catalog_provider_model(
+ provider="provider",
+ model="model",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_catalog_provider_model(
+ provider, model, workspace_id=workspace_id, request_options=request_options
+ )
+ return _response.data
diff --git a/src/onepin/providers/raw_client.py b/src/onepin/providers/raw_client.py
new file mode 100644
index 0000000..04f216b
--- /dev/null
+++ b/src/onepin/providers/raw_client.py
@@ -0,0 +1,777 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ..core.api_error import ApiError
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.http_response import AsyncHttpResponse, HttpResponse
+from ..core.jsonable_encoder import encode_path_param
+from ..core.parse_error import ParsingError
+from ..core.pydantic_utilities import parse_obj_as
+from ..core.request_options import RequestOptions
+from ..errors.not_found_error import NotFoundError
+from ..errors.unprocessable_entity_error import UnprocessableEntityError
+from ..types.api_counted_list_response_catalog_voice_out import ApiCountedListResponseCatalogVoiceOut
+from ..types.api_list_response_catalog_model_out import ApiListResponseCatalogModelOut
+from ..types.api_list_response_catalog_provider_out import ApiListResponseCatalogProviderOut
+from ..types.api_response_catalog_model_out import ApiResponseCatalogModelOut
+from ..types.api_response_catalog_provider_out import ApiResponseCatalogProviderOut
+from ..types.http_validation_error import HttpValidationError
+from pydantic import ValidationError
+
+
+class RawProvidersClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list_catalog_providers(
+ self, *, workspace_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[ApiListResponseCatalogProviderOut]:
+ """
+ List TTS providers in the public catalog.
+
+ Returns the processing (TTS) provider catalog with a per-provider model count
+ and a HATEOAS link to each provider's models. Lean / customer-safe — cost,
+ credentials, and base URLs are never exposed. Requires `X-Workspace-Id` (or a
+ workspace-bound API key with the `catalog:read` scope), matching `/voices`.
+
+ Parameters
+ ----------
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ApiListResponseCatalogProviderOut]
+ Successful Response
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "api/v1/providers",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiListResponseCatalogProviderOut,
+ parse_obj_as(
+ type_=ApiListResponseCatalogProviderOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get_catalog_provider(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ApiResponseCatalogProviderOut]:
+ """
+ Get a single TTS provider by canonical name (e.g. `cartesia`).
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ApiResponseCatalogProviderOut]
+ Successful Response
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiResponseCatalogProviderOut,
+ parse_obj_as(
+ type_=ApiResponseCatalogProviderOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def list_catalog_provider_models(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ApiListResponseCatalogModelOut]:
+ """
+ List a provider's TTS models, each with its `config_schema` and live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ApiListResponseCatalogModelOut]
+ Successful Response
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}/models",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiListResponseCatalogModelOut,
+ parse_obj_as(
+ type_=ApiListResponseCatalogModelOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def list_catalog_provider_model_voices(
+ self,
+ provider: str,
+ model: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ApiCountedListResponseCatalogVoiceOut]:
+ """
+ List platform voices catalogued under an exact `(provider, model)`.
+
+ Lean voice shape with a presigned `preview_url`. Platform catalog voices only
+ (System workspace); model-less voices are excluded. Paginated via `offset` /
+ `limit`. The flat `/voices?provider=&model=` endpoint remains for the picker.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ offset : typing.Optional[int]
+
+ limit : typing.Optional[int]
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ApiCountedListResponseCatalogVoiceOut]
+ Successful Response
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}/models/{encode_path_param(model)}/voices",
+ method="GET",
+ params={
+ "offset": offset,
+ "limit": limit,
+ },
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiCountedListResponseCatalogVoiceOut,
+ parse_obj_as(
+ type_=ApiCountedListResponseCatalogVoiceOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get_catalog_provider_model(
+ self,
+ provider: str,
+ model: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ApiResponseCatalogModelOut]:
+ """
+ Get a single TTS model — `config_schema` + live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ApiResponseCatalogModelOut]
+ Successful Response
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}/models/{encode_path_param(model)}",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiResponseCatalogModelOut,
+ parse_obj_as(
+ type_=ApiResponseCatalogModelOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawProvidersClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list_catalog_providers(
+ self, *, workspace_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[ApiListResponseCatalogProviderOut]:
+ """
+ List TTS providers in the public catalog.
+
+ Returns the processing (TTS) provider catalog with a per-provider model count
+ and a HATEOAS link to each provider's models. Lean / customer-safe — cost,
+ credentials, and base URLs are never exposed. Requires `X-Workspace-Id` (or a
+ workspace-bound API key with the `catalog:read` scope), matching `/voices`.
+
+ Parameters
+ ----------
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ApiListResponseCatalogProviderOut]
+ Successful Response
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "api/v1/providers",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiListResponseCatalogProviderOut,
+ parse_obj_as(
+ type_=ApiListResponseCatalogProviderOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get_catalog_provider(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ApiResponseCatalogProviderOut]:
+ """
+ Get a single TTS provider by canonical name (e.g. `cartesia`).
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ApiResponseCatalogProviderOut]
+ Successful Response
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiResponseCatalogProviderOut,
+ parse_obj_as(
+ type_=ApiResponseCatalogProviderOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def list_catalog_provider_models(
+ self,
+ provider: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ApiListResponseCatalogModelOut]:
+ """
+ List a provider's TTS models, each with its `config_schema` and live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ApiListResponseCatalogModelOut]
+ Successful Response
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}/models",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiListResponseCatalogModelOut,
+ parse_obj_as(
+ type_=ApiListResponseCatalogModelOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def list_catalog_provider_model_voices(
+ self,
+ provider: str,
+ model: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ApiCountedListResponseCatalogVoiceOut]:
+ """
+ List platform voices catalogued under an exact `(provider, model)`.
+
+ Lean voice shape with a presigned `preview_url`. Platform catalog voices only
+ (System workspace); model-less voices are excluded. Paginated via `offset` /
+ `limit`. The flat `/voices?provider=&model=` endpoint remains for the picker.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ offset : typing.Optional[int]
+
+ limit : typing.Optional[int]
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ApiCountedListResponseCatalogVoiceOut]
+ Successful Response
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}/models/{encode_path_param(model)}/voices",
+ method="GET",
+ params={
+ "offset": offset,
+ "limit": limit,
+ },
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiCountedListResponseCatalogVoiceOut,
+ parse_obj_as(
+ type_=ApiCountedListResponseCatalogVoiceOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get_catalog_provider_model(
+ self,
+ provider: str,
+ model: str,
+ *,
+ workspace_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ApiResponseCatalogModelOut]:
+ """
+ Get a single TTS model — `config_schema` + live `voice_count`.
+
+ Parameters
+ ----------
+ provider : str
+
+ model : str
+
+ workspace_id : typing.Optional[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ApiResponseCatalogModelOut]
+ Successful Response
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v1/providers/{encode_path_param(provider)}/models/{encode_path_param(model)}",
+ method="GET",
+ headers={
+ "X-Workspace-Id": str(workspace_id) if workspace_id is not None else None,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ApiResponseCatalogModelOut,
+ parse_obj_as(
+ type_=ApiResponseCatalogModelOut, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ HttpValidationError,
+ parse_obj_as(
+ type_=HttpValidationError, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/onepin/reference.md b/src/onepin/reference.md
index cd5d910..a3cff42 100644
--- a/src/onepin/reference.md
+++ b/src/onepin/reference.md
@@ -1713,6 +1713,12 @@ client.nodes.list_nodes()
Return full node definition + runtime options for the canvas node-config UI.
+**Deprecated (POD-612):** this version inlines the model catalog as
+`options.models_by_provider`. Use `GET /api/v2/nodes/{node_type}`, which
+replaces the inline tree with a `providers` HATEOAS href to the standalone
+catalog `/api/v1/providers`. This endpoint is kept for one release while the
+FE migrates, then removed.
+
Unlike `GET /nodes` (which returns only port schemas), this endpoint returns the
actual runtime values a user picks: available target languages (from settings),
the TTS model catalog grouped by provider, and a HATEOAS link to the workspace-
@@ -1781,6 +1787,94 @@ client.nodes.get_node_detail(
+
+
+
+
+client.nodes.get_node_detail_v2(...) -> ApiResponseNodeDetailOut
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Return full node definition + runtime options (v2 — HATEOAS catalog href).
+
+POD-612: replaces the deprecated v1 ``options.models_by_provider`` inline tree
+with a ``providers`` HATEOAS href to the standalone catalog
+``/api/v1/providers``. The FE follows that href to fetch each model's
+``config_schema`` lazily. The ``voices`` href (with its provider/model/language
+filter enums) is unchanged, so the voice picker never needs the catalog call.
+Requires ``X-Workspace-Id`` for a uniform FE contract.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from onepin import OnePinClient
+from onepin.environment import OnePinClientEnvironment
+
+client = OnePinClient(
+ token="",
+ environment=OnePinClientEnvironment.PROD,
+)
+
+client.nodes.get_node_detail_v2(
+ node_type="node_type",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**node_type:** `str`
+
+
+
+
+
+-
+
+**workspace_id:** `typing.Optional[str]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
@@ -2029,6 +2123,445 @@ client.provider_keys.delete_provider_key(
+
+
+
+
+## providers
+client.providers.list_catalog_providers(...) -> ApiListResponseCatalogProviderOut
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+List TTS providers in the public catalog.
+
+Returns the processing (TTS) provider catalog with a per-provider model count
+and a HATEOAS link to each provider's models. Lean / customer-safe — cost,
+credentials, and base URLs are never exposed. Requires `X-Workspace-Id` (or a
+workspace-bound API key with the `catalog:read` scope), matching `/voices`.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from onepin import OnePinClient
+from onepin.environment import OnePinClientEnvironment
+
+client = OnePinClient(
+ token="",
+ environment=OnePinClientEnvironment.PROD,
+)
+
+client.providers.list_catalog_providers()
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**workspace_id:** `typing.Optional[str]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.providers.get_catalog_provider(...) -> ApiResponseCatalogProviderOut
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Get a single TTS provider by canonical name (e.g. `cartesia`).
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from onepin import OnePinClient
+from onepin.environment import OnePinClientEnvironment
+
+client = OnePinClient(
+ token="",
+ environment=OnePinClientEnvironment.PROD,
+)
+
+client.providers.get_catalog_provider(
+ provider="provider",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**provider:** `str`
+
+
+
+
+
+-
+
+**workspace_id:** `typing.Optional[str]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.providers.list_catalog_provider_models(...) -> ApiListResponseCatalogModelOut
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+List a provider's TTS models, each with its `config_schema` and live `voice_count`.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from onepin import OnePinClient
+from onepin.environment import OnePinClientEnvironment
+
+client = OnePinClient(
+ token="",
+ environment=OnePinClientEnvironment.PROD,
+)
+
+client.providers.list_catalog_provider_models(
+ provider="provider",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**provider:** `str`
+
+
+
+
+
+-
+
+**workspace_id:** `typing.Optional[str]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.providers.list_catalog_provider_model_voices(...) -> ApiCountedListResponseCatalogVoiceOut
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+List platform voices catalogued under an exact `(provider, model)`.
+
+Lean voice shape with a presigned `preview_url`. Platform catalog voices only
+(System workspace); model-less voices are excluded. Paginated via `offset` /
+`limit`. The flat `/voices?provider=&model=` endpoint remains for the picker.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from onepin import OnePinClient
+from onepin.environment import OnePinClientEnvironment
+
+client = OnePinClient(
+ token="",
+ environment=OnePinClientEnvironment.PROD,
+)
+
+client.providers.list_catalog_provider_model_voices(
+ provider="provider",
+ model="model",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**provider:** `str`
+
+
+
+
+
+-
+
+**model:** `str`
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]`
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]`
+
+
+
+
+
+-
+
+**workspace_id:** `typing.Optional[str]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.providers.get_catalog_provider_model(...) -> ApiResponseCatalogModelOut
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Get a single TTS model — `config_schema` + live `voice_count`.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from onepin import OnePinClient
+from onepin.environment import OnePinClientEnvironment
+
+client = OnePinClient(
+ token="",
+ environment=OnePinClientEnvironment.PROD,
+)
+
+client.providers.get_catalog_provider_model(
+ provider="provider",
+ model="model",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**provider:** `str`
+
+
+
+
+
+-
+
+**model:** `str`
+
+
+
+
+
+-
+
+**workspace_id:** `typing.Optional[str]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
diff --git a/src/onepin/types/__init__.py b/src/onepin/types/__init__.py
index bc316d6..06c892f 100644
--- a/src/onepin/types/__init__.py
+++ b/src/onepin/types/__init__.py
@@ -7,6 +7,7 @@
if typing.TYPE_CHECKING:
from .api_counted_list_response_api_key_out import ApiCountedListResponseApiKeyOut
+ from .api_counted_list_response_catalog_voice_out import ApiCountedListResponseCatalogVoiceOut
from .api_counted_list_response_voice_out import ApiCountedListResponseVoiceOut
from .api_counted_list_response_workflow_list_item import ApiCountedListResponseWorkflowListItem
from .api_counted_list_response_workflow_run_list_item import ApiCountedListResponseWorkflowRunListItem
@@ -18,6 +19,8 @@
from .api_key_out import ApiKeyOut
from .api_key_rotate_out import ApiKeyRotateOut
from .api_key_scope import ApiKeyScope
+ from .api_list_response_catalog_model_out import ApiListResponseCatalogModelOut
+ from .api_list_response_catalog_provider_out import ApiListResponseCatalogProviderOut
from .api_list_response_customer_plan_response import ApiListResponseCustomerPlanResponse
from .api_list_response_dictionary_out import ApiListResponseDictionaryOut
from .api_list_response_node_ports_out import ApiListResponseNodePortsOut
@@ -32,6 +35,8 @@
from .api_response_api_key_rotate_out import ApiResponseApiKeyRotateOut
from .api_response_auth_whoami_out import ApiResponseAuthWhoamiOut
from .api_response_balance_response import ApiResponseBalanceResponse
+ from .api_response_catalog_model_out import ApiResponseCatalogModelOut
+ from .api_response_catalog_provider_out import ApiResponseCatalogProviderOut
from .api_response_checkout_response import ApiResponseCheckoutResponse
from .api_response_customer_plan_change_preview_response import ApiResponseCustomerPlanChangePreviewResponse
from .api_response_customer_subscription_response import ApiResponseCustomerSubscriptionResponse
@@ -74,6 +79,10 @@
from .auth_whoami_out import AuthWhoamiOut
from .auth_whoami_out_auth_kind import AuthWhoamiOutAuthKind
from .balance_response import BalanceResponse
+ from .catalog_link import CatalogLink
+ from .catalog_model_out import CatalogModelOut
+ from .catalog_provider_out import CatalogProviderOut
+ from .catalog_voice_out import CatalogVoiceOut
from .checkout_response import CheckoutResponse
from .counted_pagination_meta import CountedPaginationMeta
from .customer_plan_change_preview_response import CustomerPlanChangePreviewResponse
@@ -183,8 +192,8 @@
from .workflow_run_data_row_out import WorkflowRunDataRowOut
from .workflow_run_data_row_out_auto_corrected_status import WorkflowRunDataRowOutAutoCorrectedStatus
from .workflow_run_data_row_out_script_status import WorkflowRunDataRowOutScriptStatus
- from .workflow_run_data_scores_out import WorkflowRunDataScoresOut
- from .workflow_run_data_scores_out_accuracy_status import WorkflowRunDataScoresOutAccuracyStatus
+ from .workflow_run_data_validation_out import WorkflowRunDataValidationOut
+ from .workflow_run_data_validation_out_status import WorkflowRunDataValidationOutStatus
from .workflow_run_data_voice_out import WorkflowRunDataVoiceOut
from .workflow_run_data_voice_out_status import WorkflowRunDataVoiceOutStatus
from .workflow_run_detail_out import WorkflowRunDetailOut
@@ -217,6 +226,7 @@
from .workspace_workflows_stats_out import WorkspaceWorkflowsStatsOut
_dynamic_imports: typing.Dict[str, str] = {
"ApiCountedListResponseApiKeyOut": ".api_counted_list_response_api_key_out",
+ "ApiCountedListResponseCatalogVoiceOut": ".api_counted_list_response_catalog_voice_out",
"ApiCountedListResponseVoiceOut": ".api_counted_list_response_voice_out",
"ApiCountedListResponseWorkflowListItem": ".api_counted_list_response_workflow_list_item",
"ApiCountedListResponseWorkflowRunListItem": ".api_counted_list_response_workflow_run_list_item",
@@ -228,6 +238,8 @@
"ApiKeyOut": ".api_key_out",
"ApiKeyRotateOut": ".api_key_rotate_out",
"ApiKeyScope": ".api_key_scope",
+ "ApiListResponseCatalogModelOut": ".api_list_response_catalog_model_out",
+ "ApiListResponseCatalogProviderOut": ".api_list_response_catalog_provider_out",
"ApiListResponseCustomerPlanResponse": ".api_list_response_customer_plan_response",
"ApiListResponseDictionaryOut": ".api_list_response_dictionary_out",
"ApiListResponseNodePortsOut": ".api_list_response_node_ports_out",
@@ -242,6 +254,8 @@
"ApiResponseApiKeyRotateOut": ".api_response_api_key_rotate_out",
"ApiResponseAuthWhoamiOut": ".api_response_auth_whoami_out",
"ApiResponseBalanceResponse": ".api_response_balance_response",
+ "ApiResponseCatalogModelOut": ".api_response_catalog_model_out",
+ "ApiResponseCatalogProviderOut": ".api_response_catalog_provider_out",
"ApiResponseCheckoutResponse": ".api_response_checkout_response",
"ApiResponseCustomerPlanChangePreviewResponse": ".api_response_customer_plan_change_preview_response",
"ApiResponseCustomerSubscriptionResponse": ".api_response_customer_subscription_response",
@@ -282,6 +296,10 @@
"AuthWhoamiOut": ".auth_whoami_out",
"AuthWhoamiOutAuthKind": ".auth_whoami_out_auth_kind",
"BalanceResponse": ".balance_response",
+ "CatalogLink": ".catalog_link",
+ "CatalogModelOut": ".catalog_model_out",
+ "CatalogProviderOut": ".catalog_provider_out",
+ "CatalogVoiceOut": ".catalog_voice_out",
"CheckoutResponse": ".checkout_response",
"CountedPaginationMeta": ".counted_pagination_meta",
"CustomerPlanChangePreviewResponse": ".customer_plan_change_preview_response",
@@ -389,8 +407,8 @@
"WorkflowRunDataRowOut": ".workflow_run_data_row_out",
"WorkflowRunDataRowOutAutoCorrectedStatus": ".workflow_run_data_row_out_auto_corrected_status",
"WorkflowRunDataRowOutScriptStatus": ".workflow_run_data_row_out_script_status",
- "WorkflowRunDataScoresOut": ".workflow_run_data_scores_out",
- "WorkflowRunDataScoresOutAccuracyStatus": ".workflow_run_data_scores_out_accuracy_status",
+ "WorkflowRunDataValidationOut": ".workflow_run_data_validation_out",
+ "WorkflowRunDataValidationOutStatus": ".workflow_run_data_validation_out_status",
"WorkflowRunDataVoiceOut": ".workflow_run_data_voice_out",
"WorkflowRunDataVoiceOutStatus": ".workflow_run_data_voice_out_status",
"WorkflowRunDetailOut": ".workflow_run_detail_out",
@@ -447,6 +465,7 @@ def __dir__():
__all__ = [
"ApiCountedListResponseApiKeyOut",
+ "ApiCountedListResponseCatalogVoiceOut",
"ApiCountedListResponseVoiceOut",
"ApiCountedListResponseWorkflowListItem",
"ApiCountedListResponseWorkflowRunListItem",
@@ -458,6 +477,8 @@ def __dir__():
"ApiKeyOut",
"ApiKeyRotateOut",
"ApiKeyScope",
+ "ApiListResponseCatalogModelOut",
+ "ApiListResponseCatalogProviderOut",
"ApiListResponseCustomerPlanResponse",
"ApiListResponseDictionaryOut",
"ApiListResponseNodePortsOut",
@@ -472,6 +493,8 @@ def __dir__():
"ApiResponseApiKeyRotateOut",
"ApiResponseAuthWhoamiOut",
"ApiResponseBalanceResponse",
+ "ApiResponseCatalogModelOut",
+ "ApiResponseCatalogProviderOut",
"ApiResponseCheckoutResponse",
"ApiResponseCustomerPlanChangePreviewResponse",
"ApiResponseCustomerSubscriptionResponse",
@@ -512,6 +535,10 @@ def __dir__():
"AuthWhoamiOut",
"AuthWhoamiOutAuthKind",
"BalanceResponse",
+ "CatalogLink",
+ "CatalogModelOut",
+ "CatalogProviderOut",
+ "CatalogVoiceOut",
"CheckoutResponse",
"CountedPaginationMeta",
"CustomerPlanChangePreviewResponse",
@@ -619,8 +646,8 @@ def __dir__():
"WorkflowRunDataRowOut",
"WorkflowRunDataRowOutAutoCorrectedStatus",
"WorkflowRunDataRowOutScriptStatus",
- "WorkflowRunDataScoresOut",
- "WorkflowRunDataScoresOutAccuracyStatus",
+ "WorkflowRunDataValidationOut",
+ "WorkflowRunDataValidationOutStatus",
"WorkflowRunDataVoiceOut",
"WorkflowRunDataVoiceOutStatus",
"WorkflowRunDetailOut",
diff --git a/src/onepin/types/workflow_run_data_scores_out.py b/src/onepin/types/api_counted_list_response_catalog_voice_out.py
similarity index 52%
rename from src/onepin/types/workflow_run_data_scores_out.py
rename to src/onepin/types/api_counted_list_response_catalog_voice_out.py
index ae821ba..0709f39 100644
--- a/src/onepin/types/workflow_run_data_scores_out.py
+++ b/src/onepin/types/api_counted_list_response_catalog_voice_out.py
@@ -4,16 +4,15 @@
import pydantic
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
-from .workflow_run_data_scores_out_accuracy_status import WorkflowRunDataScoresOutAccuracyStatus
+from .catalog_voice_out import CatalogVoiceOut
+from .counted_pagination_meta import CountedPaginationMeta
+from .meta import Meta
-class WorkflowRunDataScoresOut(UniversalBaseModel):
- accuracy: typing.Optional[float] = None
- accuracy_status: WorkflowRunDataScoresOutAccuracyStatus
- accuracy_reason: typing.Optional[str] = None
- wer: typing.Optional[float] = None
- cer: typing.Optional[float] = None
- transcript: typing.Optional[str] = None
+class ApiCountedListResponseCatalogVoiceOut(UniversalBaseModel):
+ data: typing.List[CatalogVoiceOut]
+ meta: Meta
+ pagination: CountedPaginationMeta
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/onepin/types/api_key_scope.py b/src/onepin/types/api_key_scope.py
index 712b138..fd42c3b 100644
--- a/src/onepin/types/api_key_scope.py
+++ b/src/onepin/types/api_key_scope.py
@@ -14,6 +14,7 @@
"uploads:write",
"workspace:read",
"templates:read",
+ "catalog:read",
],
typing.Any,
]
diff --git a/src/onepin/types/api_list_response_catalog_model_out.py b/src/onepin/types/api_list_response_catalog_model_out.py
new file mode 100644
index 0000000..e81d108
--- /dev/null
+++ b/src/onepin/types/api_list_response_catalog_model_out.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .catalog_model_out import CatalogModelOut
+from .meta import Meta
+from .pagination_meta import PaginationMeta
+
+
+class ApiListResponseCatalogModelOut(UniversalBaseModel):
+ data: typing.List[CatalogModelOut]
+ meta: Meta
+ pagination: PaginationMeta
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/api_list_response_catalog_provider_out.py b/src/onepin/types/api_list_response_catalog_provider_out.py
new file mode 100644
index 0000000..dabeadd
--- /dev/null
+++ b/src/onepin/types/api_list_response_catalog_provider_out.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .catalog_provider_out import CatalogProviderOut
+from .meta import Meta
+from .pagination_meta import PaginationMeta
+
+
+class ApiListResponseCatalogProviderOut(UniversalBaseModel):
+ data: typing.List[CatalogProviderOut]
+ meta: Meta
+ pagination: PaginationMeta
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/api_response_catalog_model_out.py b/src/onepin/types/api_response_catalog_model_out.py
new file mode 100644
index 0000000..5dd2299
--- /dev/null
+++ b/src/onepin/types/api_response_catalog_model_out.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .catalog_model_out import CatalogModelOut
+from .meta import Meta
+
+
+class ApiResponseCatalogModelOut(UniversalBaseModel):
+ data: CatalogModelOut
+ meta: Meta
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/api_response_catalog_provider_out.py b/src/onepin/types/api_response_catalog_provider_out.py
new file mode 100644
index 0000000..0f9b381
--- /dev/null
+++ b/src/onepin/types/api_response_catalog_provider_out.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .catalog_provider_out import CatalogProviderOut
+from .meta import Meta
+
+
+class ApiResponseCatalogProviderOut(UniversalBaseModel):
+ data: CatalogProviderOut
+ meta: Meta
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/catalog_link.py b/src/onepin/types/catalog_link.py
new file mode 100644
index 0000000..5a2752e
--- /dev/null
+++ b/src/onepin/types/catalog_link.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class CatalogLink(UniversalBaseModel):
+ """
+ HATEOAS link to a nested catalog resource (HAL-FORMS-flavored).
+
+ The server emits a concrete, ready-to-call ``href`` (path segments already
+ filled in and URL-encoded) so the FE never constructs catalog URLs itself.
+ """
+
+ href: str
+ method: typing.Optional[str] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/catalog_model_out.py b/src/onepin/types/catalog_model_out.py
new file mode 100644
index 0000000..05a0ba8
--- /dev/null
+++ b/src/onepin/types/catalog_model_out.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .catalog_link import CatalogLink
+
+
+class CatalogModelOut(UniversalBaseModel):
+ """
+ Catalog model entry — lean, customer-safe.
+
+ ``config_schema`` is the per-model TTS config JSON-schema (from the provider
+ registry's Pydantic config class). ``voice_count`` is the number of active
+ platform voices catalogued under this exact ``(provider, model)``.
+ """
+
+ model: str
+ display_name: str
+ content_type: typing.Optional[str] = None
+ config_schema: typing.Optional[typing.Dict[str, typing.Any]] = None
+ voice_count: int
+ links: typing.Optional[typing.Dict[str, CatalogLink]] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/catalog_provider_out.py b/src/onepin/types/catalog_provider_out.py
new file mode 100644
index 0000000..64fb849
--- /dev/null
+++ b/src/onepin/types/catalog_provider_out.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .catalog_link import CatalogLink
+
+
+class CatalogProviderOut(UniversalBaseModel):
+ """
+ Catalog provider entry — lean, customer-safe.
+
+ Sourced from the in-memory ``ProviderRegistry`` (TTS / ``processing`` kind).
+ Cost, credentials, base_url, and enabled flags are excluded by construction.
+ """
+
+ provider: str
+ display_name: str
+ kind: str
+ model_count: int
+ links: typing.Optional[typing.Dict[str, CatalogLink]] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/catalog_voice_out.py b/src/onepin/types/catalog_voice_out.py
new file mode 100644
index 0000000..a46102c
--- /dev/null
+++ b/src/onepin/types/catalog_voice_out.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .voice_accent import VoiceAccent
+from .voice_age import VoiceAge
+from .voice_gender import VoiceGender
+
+
+class CatalogVoiceOut(UniversalBaseModel):
+ """
+ Lean voice entry for ``/providers/{p}/models/{m}/voices``.
+
+ ``provider``/``model`` are implied by the path, so they are omitted here.
+ ``preview_url`` is a short-lived presigned S3 URL for the preview sample.
+ """
+
+ id: str
+ provider_voice_id: str
+ name: str
+ gender: typing.Optional[VoiceGender] = None
+ age: typing.Optional[VoiceAge] = None
+ accent: typing.Optional[VoiceAccent] = None
+ supported_languages: typing.Optional[typing.List[str]] = None
+ preview_url: typing.Optional[str] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/node_type.py b/src/onepin/types/node_type.py
index 36281c2..4794994 100644
--- a/src/onepin/types/node_type.py
+++ b/src/onepin/types/node_type.py
@@ -10,6 +10,7 @@
"processing_generator",
"sink_preview",
"validator_error_rate",
+ "validator_naturalness",
],
typing.Any,
]
diff --git a/src/onepin/types/workflow_run_data_card_out.py b/src/onepin/types/workflow_run_data_card_out.py
index 56a1ea1..b4b9e16 100644
--- a/src/onepin/types/workflow_run_data_card_out.py
+++ b/src/onepin/types/workflow_run_data_card_out.py
@@ -6,7 +6,7 @@
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from .workflow_run_data_audio_out import WorkflowRunDataAudioOut
from .workflow_run_data_card_out_waveform_status import WorkflowRunDataCardOutWaveformStatus
-from .workflow_run_data_scores_out import WorkflowRunDataScoresOut
+from .workflow_run_data_validation_out import WorkflowRunDataValidationOut
from .workflow_run_data_voice_out import WorkflowRunDataVoiceOut
@@ -18,7 +18,7 @@ class WorkflowRunDataCardOut(UniversalBaseModel):
locale_code: typing.Optional[str] = None
script: typing.Optional[str] = None
audio: WorkflowRunDataAudioOut
- scores: WorkflowRunDataScoresOut
+ validations: typing.Optional[typing.List[WorkflowRunDataValidationOut]] = None
voice: WorkflowRunDataVoiceOut
waveform_url: typing.Optional[str] = None
waveform_status: typing.Optional[WorkflowRunDataCardOutWaveformStatus] = None
diff --git a/src/onepin/types/workflow_run_data_validation_out.py b/src/onepin/types/workflow_run_data_validation_out.py
new file mode 100644
index 0000000..71299fc
--- /dev/null
+++ b/src/onepin/types/workflow_run_data_validation_out.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workflow_run_data_validation_out_status import WorkflowRunDataValidationOutStatus
+
+
+class WorkflowRunDataValidationOut(UniversalBaseModel):
+ """
+ Per-validator scoring entry for a single line (POD-599).
+
+ WER entries populate ``wer``/``cer``/``transcript``; naturalness entries
+ populate ``score`` (0-100). All fields except ``kind`` and ``status`` are
+ optional so future validator kinds can omit inapplicable fields.
+ """
+
+ kind: str
+ label: typing.Optional[str] = None
+ score: typing.Optional[float] = None
+ status: WorkflowRunDataValidationOutStatus
+ reason: typing.Optional[str] = None
+ wer: typing.Optional[float] = None
+ cer: typing.Optional[float] = None
+ transcript: typing.Optional[str] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/onepin/types/workflow_run_data_scores_out_accuracy_status.py b/src/onepin/types/workflow_run_data_validation_out_status.py
similarity index 75%
rename from src/onepin/types/workflow_run_data_scores_out_accuracy_status.py
rename to src/onepin/types/workflow_run_data_validation_out_status.py
index cdea3b8..d23296e 100644
--- a/src/onepin/types/workflow_run_data_scores_out_accuracy_status.py
+++ b/src/onepin/types/workflow_run_data_validation_out_status.py
@@ -2,6 +2,6 @@
import typing
-WorkflowRunDataScoresOutAccuracyStatus = typing.Union[
+WorkflowRunDataValidationOutStatus = typing.Union[
typing.Literal["available", "not_ready", "unsupported", "unavailable"], typing.Any
]