From 7c02dfddcdc856f4605d2e04dcce3600504285c8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 11:13:20 +0000
Subject: [PATCH 1/4] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index c2c8b51..5641f2f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 27
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/moderation-api/moderation-api-984af2e010ad1bf22f607ee96f27260ecf421c0ce5664839799b92eb349f9255.yml
-openapi_spec_hash: bbd9dbdc1a4b7b0ad35aa08fba66e676
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/moderation-api/moderation-api-d8e6cb2be89d38dfda3f4a745a813223b67d519943e660658a90ce2771ee1702.yml
+openapi_spec_hash: 7bcba0df8e8cb1c5a2d02efc3d6c0fa5
config_hash: 9d144cc6c49d3fd53e5b4472c1e22165
From c726781aac9fa741cfdde6c547caeccfe29ed0b8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 11:15:43 +0000
Subject: [PATCH 2/4] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 5641f2f..9c7c5c1 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 27
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/moderation-api/moderation-api-d8e6cb2be89d38dfda3f4a745a813223b67d519943e660658a90ce2771ee1702.yml
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/moderation-api/moderation-api-6644e98b7909ae773e2140200f43feaf75382b94fd18918e1ea0f630419839c9.yml
openapi_spec_hash: 7bcba0df8e8cb1c5a2d02efc3d6c0fa5
-config_hash: 9d144cc6c49d3fd53e5b4472c1e22165
+config_hash: 827ffbc2bec8c435226c9080d32e6833
From 4f4bf9f2d1e2b6c5ddea270e16fd4d8f10600618 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 11:25:32 +0000
Subject: [PATCH 3/4] feat(api): webhooks and stream
---
.stats.yml | 6 +-
api.md | 35 +
src/moderation_api/_client.py | 86 ++-
src/moderation_api/resources/__init__.py | 28 +
src/moderation_api/resources/content.py | 120 +++-
.../resources/webhook_secret.py | 143 ++++
src/moderation_api/resources/webhooks.py | 618 ++++++++++++++++++
src/moderation_api/types/__init__.py | 8 +
.../types/webhook_create_params.py | 46 ++
.../types/webhook_create_response.py | 55 ++
.../types/webhook_delete_response.py | 13 +
.../types/webhook_list_response.py | 58 ++
.../types/webhook_retrieve_response.py | 55 ++
.../types/webhook_secret_retrieve_response.py | 14 +
.../types/webhook_update_params.py | 44 ++
.../types/webhook_update_response.py | 55 ++
tests/api_resources/test_content.py | 68 ++
tests/api_resources/test_webhook_secret.py | 80 +++
tests/api_resources/test_webhooks.py | 464 +++++++++++++
19 files changed, 1989 insertions(+), 7 deletions(-)
create mode 100644 src/moderation_api/resources/webhook_secret.py
create mode 100644 src/moderation_api/resources/webhooks.py
create mode 100644 src/moderation_api/types/webhook_create_params.py
create mode 100644 src/moderation_api/types/webhook_create_response.py
create mode 100644 src/moderation_api/types/webhook_delete_response.py
create mode 100644 src/moderation_api/types/webhook_list_response.py
create mode 100644 src/moderation_api/types/webhook_retrieve_response.py
create mode 100644 src/moderation_api/types/webhook_secret_retrieve_response.py
create mode 100644 src/moderation_api/types/webhook_update_params.py
create mode 100644 src/moderation_api/types/webhook_update_response.py
create mode 100644 tests/api_resources/test_webhook_secret.py
create mode 100644 tests/api_resources/test_webhooks.py
diff --git a/.stats.yml b/.stats.yml
index 9c7c5c1..97520a7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 27
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/moderation-api/moderation-api-6644e98b7909ae773e2140200f43feaf75382b94fd18918e1ea0f630419839c9.yml
+configured_endpoints: 34
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/moderation-api/moderation-api-407f949d64421d5454ec0f4b518a19c14daa41312b6efa370eac631e495ca44a.yml
openapi_spec_hash: 7bcba0df8e8cb1c5a2d02efc3d6c0fa5
-config_hash: 827ffbc2bec8c435226c9080d32e6833
+config_hash: 6e090a71f4d354167d93b4534a0e7f05
diff --git a/api.md b/api.md
index 478b6df..bf7cc9f 100644
--- a/api.md
+++ b/api.md
@@ -92,6 +92,7 @@ from moderation_api.types import ContentSubmitResponse
Methods:
+- client.content.stream() -> None
- client.content.submit(\*\*params) -> ContentSubmitResponse
# Account
@@ -151,3 +152,37 @@ Methods:
- client.wordlist.words.add(id, \*\*params) -> WordAddResponse
- client.wordlist.words.remove(id, \*\*params) -> WordRemoveResponse
+
+# Webhooks
+
+Types:
+
+```python
+from moderation_api.types import (
+ WebhookCreateResponse,
+ WebhookRetrieveResponse,
+ WebhookUpdateResponse,
+ WebhookListResponse,
+ WebhookDeleteResponse,
+)
+```
+
+Methods:
+
+- client.webhooks.create(\*\*params) -> WebhookCreateResponse
+- client.webhooks.retrieve(id) -> WebhookRetrieveResponse
+- client.webhooks.update(id, \*\*params) -> WebhookUpdateResponse
+- client.webhooks.list() -> WebhookListResponse
+- client.webhooks.delete(id) -> WebhookDeleteResponse
+
+# WebhookSecret
+
+Types:
+
+```python
+from moderation_api.types import WebhookSecretRetrieveResponse
+```
+
+Methods:
+
+- client.webhook_secret.retrieve() -> WebhookSecretRetrieveResponse
diff --git a/src/moderation_api/_client.py b/src/moderation_api/_client.py
index fc2797e..6bdd9f5 100644
--- a/src/moderation_api/_client.py
+++ b/src/moderation_api/_client.py
@@ -35,12 +35,14 @@
)
if TYPE_CHECKING:
- from .resources import auth, queue, account, actions, authors, content, wordlist
+ from .resources import auth, queue, account, actions, authors, content, webhooks, wordlist, webhook_secret
from .resources.auth import AuthResource, AsyncAuthResource
from .resources.account import AccountResource, AsyncAccountResource
from .resources.authors import AuthorsResource, AsyncAuthorsResource
from .resources.content import ContentResource, AsyncContentResource
+ from .resources.webhooks import WebhooksResource, AsyncWebhooksResource
from .resources.queue.queue import QueueResource, AsyncQueueResource
+ from .resources.webhook_secret import WebhookSecretResource, AsyncWebhookSecretResource
from .resources.actions.actions import ActionsResource, AsyncActionsResource
from .resources.wordlist.wordlist import WordlistResource, AsyncWordlistResource
@@ -97,6 +99,7 @@ def __init__(
if base_url is None:
base_url = os.environ.get("MODERATION_API_BASE_URL")
+ self._base_url_overridden = base_url is not None
if base_url is None:
base_url = f"https://api.moderationapi.com/v1"
@@ -162,6 +165,18 @@ def wordlist(self) -> WordlistResource:
return WordlistResource(self)
+ @cached_property
+ def webhooks(self) -> WebhooksResource:
+ from .resources.webhooks import WebhooksResource
+
+ return WebhooksResource(self)
+
+ @cached_property
+ def webhook_secret(self) -> WebhookSecretResource:
+ from .resources.webhook_secret import WebhookSecretResource
+
+ return WebhookSecretResource(self)
+
@cached_property
def with_raw_response(self) -> ModerationAPIWithRawResponse:
return ModerationAPIWithRawResponse(self)
@@ -226,7 +241,7 @@ def copy(
params = set_default_query
http_client = http_client or self._client
- return self.__class__(
+ client = self.__class__(
secret_key=secret_key or self.secret_key,
base_url=base_url or self.base_url,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
@@ -236,6 +251,8 @@ def copy(
default_query=params,
**_extra_kwargs,
)
+ client._base_url_overridden = self._base_url_overridden or base_url is not None
+ return client
# Alias for `copy` for nicer inline usage, e.g.
# client.with_options(timeout=10).foo.create(...)
@@ -316,6 +333,7 @@ def __init__(
if base_url is None:
base_url = os.environ.get("MODERATION_API_BASE_URL")
+ self._base_url_overridden = base_url is not None
if base_url is None:
base_url = f"https://api.moderationapi.com/v1"
@@ -381,6 +399,18 @@ def wordlist(self) -> AsyncWordlistResource:
return AsyncWordlistResource(self)
+ @cached_property
+ def webhooks(self) -> AsyncWebhooksResource:
+ from .resources.webhooks import AsyncWebhooksResource
+
+ return AsyncWebhooksResource(self)
+
+ @cached_property
+ def webhook_secret(self) -> AsyncWebhookSecretResource:
+ from .resources.webhook_secret import AsyncWebhookSecretResource
+
+ return AsyncWebhookSecretResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncModerationAPIWithRawResponse:
return AsyncModerationAPIWithRawResponse(self)
@@ -445,7 +475,7 @@ def copy(
params = set_default_query
http_client = http_client or self._client
- return self.__class__(
+ client = self.__class__(
secret_key=secret_key or self.secret_key,
base_url=base_url or self.base_url,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
@@ -455,6 +485,8 @@ def copy(
default_query=params,
**_extra_kwargs,
)
+ client._base_url_overridden = self._base_url_overridden or base_url is not None
+ return client
# Alias for `copy` for nicer inline usage, e.g.
# client.with_options(timeout=10).foo.create(...)
@@ -542,6 +574,18 @@ def wordlist(self) -> wordlist.WordlistResourceWithRawResponse:
return WordlistResourceWithRawResponse(self._client.wordlist)
+ @cached_property
+ def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse:
+ from .resources.webhooks import WebhooksResourceWithRawResponse
+
+ return WebhooksResourceWithRawResponse(self._client.webhooks)
+
+ @cached_property
+ def webhook_secret(self) -> webhook_secret.WebhookSecretResourceWithRawResponse:
+ from .resources.webhook_secret import WebhookSecretResourceWithRawResponse
+
+ return WebhookSecretResourceWithRawResponse(self._client.webhook_secret)
+
class AsyncModerationAPIWithRawResponse:
_client: AsyncModerationAPI
@@ -591,6 +635,18 @@ def wordlist(self) -> wordlist.AsyncWordlistResourceWithRawResponse:
return AsyncWordlistResourceWithRawResponse(self._client.wordlist)
+ @cached_property
+ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse:
+ from .resources.webhooks import AsyncWebhooksResourceWithRawResponse
+
+ return AsyncWebhooksResourceWithRawResponse(self._client.webhooks)
+
+ @cached_property
+ def webhook_secret(self) -> webhook_secret.AsyncWebhookSecretResourceWithRawResponse:
+ from .resources.webhook_secret import AsyncWebhookSecretResourceWithRawResponse
+
+ return AsyncWebhookSecretResourceWithRawResponse(self._client.webhook_secret)
+
class ModerationAPIWithStreamedResponse:
_client: ModerationAPI
@@ -640,6 +696,18 @@ def wordlist(self) -> wordlist.WordlistResourceWithStreamingResponse:
return WordlistResourceWithStreamingResponse(self._client.wordlist)
+ @cached_property
+ def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse:
+ from .resources.webhooks import WebhooksResourceWithStreamingResponse
+
+ return WebhooksResourceWithStreamingResponse(self._client.webhooks)
+
+ @cached_property
+ def webhook_secret(self) -> webhook_secret.WebhookSecretResourceWithStreamingResponse:
+ from .resources.webhook_secret import WebhookSecretResourceWithStreamingResponse
+
+ return WebhookSecretResourceWithStreamingResponse(self._client.webhook_secret)
+
class AsyncModerationAPIWithStreamedResponse:
_client: AsyncModerationAPI
@@ -689,6 +757,18 @@ def wordlist(self) -> wordlist.AsyncWordlistResourceWithStreamingResponse:
return AsyncWordlistResourceWithStreamingResponse(self._client.wordlist)
+ @cached_property
+ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse:
+ from .resources.webhooks import AsyncWebhooksResourceWithStreamingResponse
+
+ return AsyncWebhooksResourceWithStreamingResponse(self._client.webhooks)
+
+ @cached_property
+ def webhook_secret(self) -> webhook_secret.AsyncWebhookSecretResourceWithStreamingResponse:
+ from .resources.webhook_secret import AsyncWebhookSecretResourceWithStreamingResponse
+
+ return AsyncWebhookSecretResourceWithStreamingResponse(self._client.webhook_secret)
+
Client = ModerationAPI
diff --git a/src/moderation_api/resources/__init__.py b/src/moderation_api/resources/__init__.py
index 5d14523..2a7ead1 100644
--- a/src/moderation_api/resources/__init__.py
+++ b/src/moderation_api/resources/__init__.py
@@ -48,6 +48,14 @@
ContentResourceWithStreamingResponse,
AsyncContentResourceWithStreamingResponse,
)
+from .webhooks import (
+ WebhooksResource,
+ AsyncWebhooksResource,
+ WebhooksResourceWithRawResponse,
+ AsyncWebhooksResourceWithRawResponse,
+ WebhooksResourceWithStreamingResponse,
+ AsyncWebhooksResourceWithStreamingResponse,
+)
from .wordlist import (
WordlistResource,
AsyncWordlistResource,
@@ -56,6 +64,14 @@
WordlistResourceWithStreamingResponse,
AsyncWordlistResourceWithStreamingResponse,
)
+from .webhook_secret import (
+ WebhookSecretResource,
+ AsyncWebhookSecretResource,
+ WebhookSecretResourceWithRawResponse,
+ AsyncWebhookSecretResourceWithRawResponse,
+ WebhookSecretResourceWithStreamingResponse,
+ AsyncWebhookSecretResourceWithStreamingResponse,
+)
__all__ = [
"AuthorsResource",
@@ -100,4 +116,16 @@
"AsyncWordlistResourceWithRawResponse",
"WordlistResourceWithStreamingResponse",
"AsyncWordlistResourceWithStreamingResponse",
+ "WebhooksResource",
+ "AsyncWebhooksResource",
+ "WebhooksResourceWithRawResponse",
+ "AsyncWebhooksResourceWithRawResponse",
+ "WebhooksResourceWithStreamingResponse",
+ "AsyncWebhooksResourceWithStreamingResponse",
+ "WebhookSecretResource",
+ "AsyncWebhookSecretResource",
+ "WebhookSecretResourceWithRawResponse",
+ "AsyncWebhookSecretResourceWithRawResponse",
+ "WebhookSecretResourceWithStreamingResponse",
+ "AsyncWebhookSecretResourceWithStreamingResponse",
]
diff --git a/src/moderation_api/resources/content.py b/src/moderation_api/resources/content.py
index 7938459..9d68f93 100644
--- a/src/moderation_api/resources/content.py
+++ b/src/moderation_api/resources/content.py
@@ -8,7 +8,7 @@
import httpx
from ..types import content_submit_params
-from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from .._utils import maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
@@ -44,6 +44,59 @@ def with_streaming_response(self) -> ContentResourceWithStreamingResponse:
"""
return ContentResourceWithStreamingResponse(self)
+ def stream(
+ self,
+ *,
+ sec_web_socket_protocol: Literal["moderationapi.v1"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Open a WebSocket to moderate live voice/call audio in real time.
+
+ Speech is
+ transcribed and each finalized utterance is moderated by your enabled text
+ policies; you receive a verdict per utterance as it's spoken.
+
+ **This is a WebSocket upgrade, not a regular HTTP call.** The request body below
+ documents the frames you _send_ over the socket; the `101` response documents
+ the events you _receive_.
+
+ - **Auth:** `Authorization: Bearer ` on the upgrade. A missing/invalid
+ key closes `4401`; voice not enabled on the plan/channel closes `4403`.
+ - **Subprotocol:** request `moderationapi.v1`.
+ - **Flow:** send one `start` frame, then `media` frames as audio arrives, then
+ `stop` (or disconnect). You receive `session.started`, `utterance.final` per
+ utterance, optional `utterance.partial`/`warning`, and `session.ended`.
+ - **Close codes:** `1000` normal · `1011` server error · `4400` bad request ·
+ `4401` auth failed · `4403` voice not enabled · `4429` concurrency limit.
+
+ See the
+ [Real-time voice guide](https://docs.moderationapi.com/content-moderation/real-time-voice)
+ for the full walkthrough and code examples.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers.update({"Sec-WebSocket-Protocol": str(sec_web_socket_protocol)})
+ return self._get(
+ "/stream" if self._client._base_url_overridden else "wss://voice.moderationapi.com/v1/stream",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
def submit(
self,
*,
@@ -147,6 +200,59 @@ def with_streaming_response(self) -> AsyncContentResourceWithStreamingResponse:
"""
return AsyncContentResourceWithStreamingResponse(self)
+ async def stream(
+ self,
+ *,
+ sec_web_socket_protocol: Literal["moderationapi.v1"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Open a WebSocket to moderate live voice/call audio in real time.
+
+ Speech is
+ transcribed and each finalized utterance is moderated by your enabled text
+ policies; you receive a verdict per utterance as it's spoken.
+
+ **This is a WebSocket upgrade, not a regular HTTP call.** The request body below
+ documents the frames you _send_ over the socket; the `101` response documents
+ the events you _receive_.
+
+ - **Auth:** `Authorization: Bearer ` on the upgrade. A missing/invalid
+ key closes `4401`; voice not enabled on the plan/channel closes `4403`.
+ - **Subprotocol:** request `moderationapi.v1`.
+ - **Flow:** send one `start` frame, then `media` frames as audio arrives, then
+ `stop` (or disconnect). You receive `session.started`, `utterance.final` per
+ utterance, optional `utterance.partial`/`warning`, and `session.ended`.
+ - **Close codes:** `1000` normal · `1011` server error · `4400` bad request ·
+ `4401` auth failed · `4403` voice not enabled · `4429` concurrency limit.
+
+ See the
+ [Real-time voice guide](https://docs.moderationapi.com/content-moderation/real-time-voice)
+ for the full walkthrough and code examples.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers.update({"Sec-WebSocket-Protocol": str(sec_web_socket_protocol)})
+ return await self._get(
+ "/stream" if self._client._base_url_overridden else "wss://voice.moderationapi.com/v1/stream",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
async def submit(
self,
*,
@@ -234,6 +340,9 @@ class ContentResourceWithRawResponse:
def __init__(self, content: ContentResource) -> None:
self._content = content
+ self.stream = to_raw_response_wrapper(
+ content.stream,
+ )
self.submit = to_raw_response_wrapper(
content.submit,
)
@@ -243,6 +352,9 @@ class AsyncContentResourceWithRawResponse:
def __init__(self, content: AsyncContentResource) -> None:
self._content = content
+ self.stream = async_to_raw_response_wrapper(
+ content.stream,
+ )
self.submit = async_to_raw_response_wrapper(
content.submit,
)
@@ -252,6 +364,9 @@ class ContentResourceWithStreamingResponse:
def __init__(self, content: ContentResource) -> None:
self._content = content
+ self.stream = to_streamed_response_wrapper(
+ content.stream,
+ )
self.submit = to_streamed_response_wrapper(
content.submit,
)
@@ -261,6 +376,9 @@ class AsyncContentResourceWithStreamingResponse:
def __init__(self, content: AsyncContentResource) -> None:
self._content = content
+ self.stream = async_to_streamed_response_wrapper(
+ content.stream,
+ )
self.submit = async_to_streamed_response_wrapper(
content.submit,
)
diff --git a/src/moderation_api/resources/webhook_secret.py b/src/moderation_api/resources/webhook_secret.py
new file mode 100644
index 0000000..0bbd008
--- /dev/null
+++ b/src/moderation_api/resources/webhook_secret.py
@@ -0,0 +1,143 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .._types import Body, Query, Headers, NotGiven, not_given
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.webhook_secret_retrieve_response import WebhookSecretRetrieveResponse
+
+__all__ = ["WebhookSecretResource", "AsyncWebhookSecretResource"]
+
+
+class WebhookSecretResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> WebhookSecretResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return WebhookSecretResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> WebhookSecretResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response
+ """
+ return WebhookSecretResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookSecretRetrieveResponse:
+ """
+ Get the signing secret used to sign webhook deliveries for this project,
+ creating one if none exists yet. Verify deliveries by comparing the
+ `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded.
+ """
+ return self._get(
+ "/webhook-secret",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookSecretRetrieveResponse,
+ )
+
+
+class AsyncWebhookSecretResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncWebhookSecretResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncWebhookSecretResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncWebhookSecretResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response
+ """
+ return AsyncWebhookSecretResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookSecretRetrieveResponse:
+ """
+ Get the signing secret used to sign webhook deliveries for this project,
+ creating one if none exists yet. Verify deliveries by comparing the
+ `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded.
+ """
+ return await self._get(
+ "/webhook-secret",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookSecretRetrieveResponse,
+ )
+
+
+class WebhookSecretResourceWithRawResponse:
+ def __init__(self, webhook_secret: WebhookSecretResource) -> None:
+ self._webhook_secret = webhook_secret
+
+ self.retrieve = to_raw_response_wrapper(
+ webhook_secret.retrieve,
+ )
+
+
+class AsyncWebhookSecretResourceWithRawResponse:
+ def __init__(self, webhook_secret: AsyncWebhookSecretResource) -> None:
+ self._webhook_secret = webhook_secret
+
+ self.retrieve = async_to_raw_response_wrapper(
+ webhook_secret.retrieve,
+ )
+
+
+class WebhookSecretResourceWithStreamingResponse:
+ def __init__(self, webhook_secret: WebhookSecretResource) -> None:
+ self._webhook_secret = webhook_secret
+
+ self.retrieve = to_streamed_response_wrapper(
+ webhook_secret.retrieve,
+ )
+
+
+class AsyncWebhookSecretResourceWithStreamingResponse:
+ def __init__(self, webhook_secret: AsyncWebhookSecretResource) -> None:
+ self._webhook_secret = webhook_secret
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ webhook_secret.retrieve,
+ )
diff --git a/src/moderation_api/resources/webhooks.py b/src/moderation_api/resources/webhooks.py
new file mode 100644
index 0000000..1366369
--- /dev/null
+++ b/src/moderation_api/resources/webhooks.py
@@ -0,0 +1,618 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import webhook_create_params, webhook_update_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.webhook_list_response import WebhookListResponse
+from ..types.webhook_create_response import WebhookCreateResponse
+from ..types.webhook_delete_response import WebhookDeleteResponse
+from ..types.webhook_update_response import WebhookUpdateResponse
+from ..types.webhook_retrieve_response import WebhookRetrieveResponse
+
+__all__ = ["WebhooksResource", "AsyncWebhooksResource"]
+
+
+class WebhooksResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> WebhooksResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return WebhooksResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> WebhooksResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response
+ """
+ return WebhooksResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ],
+ name: str,
+ url: str,
+ description: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookCreateResponse:
+ """Create a webhook subscribed to one or more event types.
+
+ Deliveries use the v2
+ envelope and are signed with the project signing secret (see the signing secret
+ endpoint).
+
+ Args:
+ event_types: Event types this webhook subscribes to. One webhook URL receives all events you
+ list here.
+
+ name: The webhook's name, used to identify it in the dashboard
+
+ url: The webhook's URL. We'll call this URL when an event occurs.
+
+ description: The webhook's description
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/webhooks",
+ body=maybe_transform(
+ {
+ "event_types": event_types,
+ "name": name,
+ "url": url,
+ "description": description,
+ },
+ webhook_create_params.WebhookCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookCreateResponse,
+ )
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookRetrieveResponse:
+ """
+ Get a webhook by ID.
+
+ Args:
+ id: The ID of the webhook to get.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookRetrieveResponse,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ description: Optional[str] | Omit = omit,
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ]
+ | Omit = omit,
+ name: str | Omit = omit,
+ url: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookUpdateResponse:
+ """Update a webhook.
+
+ Legacy v1 webhooks are read-only: delete them and create a new
+ webhook instead.
+
+ Args:
+ id: The ID of the webhook to update.
+
+ description: The webhook's description
+
+ event_types: Event types this webhook subscribes to. One webhook URL receives all events you
+ list here.
+
+ name: The webhook's name, used to identify it in the dashboard
+
+ url: The webhook's URL. We'll call this URL when an event occurs.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._put(
+ path_template("/webhooks/{id}", id=id),
+ body=maybe_transform(
+ {
+ "description": description,
+ "event_types": event_types,
+ "name": name,
+ "url": url,
+ },
+ webhook_update_params.WebhookUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookListResponse:
+ """List all webhooks for the authenticated project."""
+ return self._get(
+ "/webhooks",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookListResponse,
+ )
+
+ def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookDeleteResponse:
+ """
+ Delete a webhook.
+
+ Args:
+ id: The ID of the webhook to delete.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._delete(
+ path_template("/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookDeleteResponse,
+ )
+
+
+class AsyncWebhooksResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncWebhooksResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncWebhooksResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncWebhooksResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/moderation-api/sdk-python#with_streaming_response
+ """
+ return AsyncWebhooksResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ],
+ name: str,
+ url: str,
+ description: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookCreateResponse:
+ """Create a webhook subscribed to one or more event types.
+
+ Deliveries use the v2
+ envelope and are signed with the project signing secret (see the signing secret
+ endpoint).
+
+ Args:
+ event_types: Event types this webhook subscribes to. One webhook URL receives all events you
+ list here.
+
+ name: The webhook's name, used to identify it in the dashboard
+
+ url: The webhook's URL. We'll call this URL when an event occurs.
+
+ description: The webhook's description
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/webhooks",
+ body=await async_maybe_transform(
+ {
+ "event_types": event_types,
+ "name": name,
+ "url": url,
+ "description": description,
+ },
+ webhook_create_params.WebhookCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookCreateResponse,
+ )
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookRetrieveResponse:
+ """
+ Get a webhook by ID.
+
+ Args:
+ id: The ID of the webhook to get.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookRetrieveResponse,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ description: Optional[str] | Omit = omit,
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ]
+ | Omit = omit,
+ name: str | Omit = omit,
+ url: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookUpdateResponse:
+ """Update a webhook.
+
+ Legacy v1 webhooks are read-only: delete them and create a new
+ webhook instead.
+
+ Args:
+ id: The ID of the webhook to update.
+
+ description: The webhook's description
+
+ event_types: Event types this webhook subscribes to. One webhook URL receives all events you
+ list here.
+
+ name: The webhook's name, used to identify it in the dashboard
+
+ url: The webhook's URL. We'll call this URL when an event occurs.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._put(
+ path_template("/webhooks/{id}", id=id),
+ body=await async_maybe_transform(
+ {
+ "description": description,
+ "event_types": event_types,
+ "name": name,
+ "url": url,
+ },
+ webhook_update_params.WebhookUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookUpdateResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookListResponse:
+ """List all webhooks for the authenticated project."""
+ return await self._get(
+ "/webhooks",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookListResponse,
+ )
+
+ async def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookDeleteResponse:
+ """
+ Delete a webhook.
+
+ Args:
+ id: The ID of the webhook to delete.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._delete(
+ path_template("/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WebhookDeleteResponse,
+ )
+
+
+class WebhooksResourceWithRawResponse:
+ def __init__(self, webhooks: WebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = to_raw_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ webhooks.update,
+ )
+ self.list = to_raw_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ webhooks.delete,
+ )
+
+
+class AsyncWebhooksResourceWithRawResponse:
+ def __init__(self, webhooks: AsyncWebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = async_to_raw_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ webhooks.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ webhooks.delete,
+ )
+
+
+class WebhooksResourceWithStreamingResponse:
+ def __init__(self, webhooks: WebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = to_streamed_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ webhooks.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ webhooks.delete,
+ )
+
+
+class AsyncWebhooksResourceWithStreamingResponse:
+ def __init__(self, webhooks: AsyncWebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = async_to_streamed_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ webhooks.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ webhooks.delete,
+ )
diff --git a/src/moderation_api/types/__init__.py b/src/moderation_api/types/__init__.py
index 0db1c73..217c8c2 100644
--- a/src/moderation_api/types/__init__.py
+++ b/src/moderation_api/types/__init__.py
@@ -13,6 +13,9 @@
from .author_update_params import AuthorUpdateParams as AuthorUpdateParams
from .account_list_response import AccountListResponse as AccountListResponse
from .content_submit_params import ContentSubmitParams as ContentSubmitParams
+from .webhook_create_params import WebhookCreateParams as WebhookCreateParams
+from .webhook_list_response import WebhookListResponse as WebhookListResponse
+from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams
from .action_create_response import ActionCreateResponse as ActionCreateResponse
from .action_delete_response import ActionDeleteResponse as ActionDeleteResponse
from .action_update_response import ActionUpdateResponse as ActionUpdateResponse
@@ -25,11 +28,16 @@
from .wordlist_update_params import WordlistUpdateParams as WordlistUpdateParams
from .content_submit_response import ContentSubmitResponse as ContentSubmitResponse
from .queue_retrieve_response import QueueRetrieveResponse as QueueRetrieveResponse
+from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse
+from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse
+from .webhook_update_response import WebhookUpdateResponse as WebhookUpdateResponse
from .action_retrieve_response import ActionRetrieveResponse as ActionRetrieveResponse
from .author_retrieve_response import AuthorRetrieveResponse as AuthorRetrieveResponse
from .queue_get_stats_response import QueueGetStatsResponse as QueueGetStatsResponse
from .wordlist_update_response import WordlistUpdateResponse as WordlistUpdateResponse
+from .webhook_retrieve_response import WebhookRetrieveResponse as WebhookRetrieveResponse
from .wordlist_retrieve_response import WordlistRetrieveResponse as WordlistRetrieveResponse
+from .webhook_secret_retrieve_response import WebhookSecretRetrieveResponse as WebhookSecretRetrieveResponse
from .wordlist_get_embedding_status_response import (
WordlistGetEmbeddingStatusResponse as WordlistGetEmbeddingStatusResponse,
)
diff --git a/src/moderation_api/types/webhook_create_params.py b/src/moderation_api/types/webhook_create_params.py
new file mode 100644
index 0000000..d1b1753
--- /dev/null
+++ b/src/moderation_api/types/webhook_create_params.py
@@ -0,0 +1,46 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["WebhookCreateParams"]
+
+
+class WebhookCreateParams(TypedDict, total=False):
+ event_types: Required[
+ Annotated[
+ List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ],
+ PropertyInfo(alias="eventTypes"),
+ ]
+ ]
+ """Event types this webhook subscribes to.
+
+ One webhook URL receives all events you list here.
+ """
+
+ name: Required[str]
+ """The webhook's name, used to identify it in the dashboard"""
+
+ url: Required[str]
+ """The webhook's URL. We'll call this URL when an event occurs."""
+
+ description: Optional[str]
+ """The webhook's description"""
diff --git a/src/moderation_api/types/webhook_create_response.py b/src/moderation_api/types/webhook_create_response.py
new file mode 100644
index 0000000..48c01b8
--- /dev/null
+++ b/src/moderation_api/types/webhook_create_response.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["WebhookCreateResponse"]
+
+
+class WebhookCreateResponse(BaseModel):
+ id: str
+ """The ID of the webhook."""
+
+ created_at: str = FieldInfo(alias="createdAt")
+ """The date the webhook was created."""
+
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ] = FieldInfo(alias="eventTypes")
+ """Event types this webhook subscribes to.
+
+ Empty for legacy v1 webhooks, which subscribe via their single deprecated `type`
+ instead.
+ """
+
+ name: str
+ """The webhook's name."""
+
+ payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion")
+ """Payload envelope version.
+
+ V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only
+ via this API.
+ """
+
+ url: str
+ """The URL we call when a subscribed event occurs."""
+
+ description: Optional[str] = None
+ """The webhook's description."""
diff --git a/src/moderation_api/types/webhook_delete_response.py b/src/moderation_api/types/webhook_delete_response.py
new file mode 100644
index 0000000..b66c864
--- /dev/null
+++ b/src/moderation_api/types/webhook_delete_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["WebhookDeleteResponse"]
+
+
+class WebhookDeleteResponse(BaseModel):
+ id: str
+ """The ID of the webhook."""
+
+ deleted: bool
+ """Whether the webhook was deleted."""
diff --git a/src/moderation_api/types/webhook_list_response.py b/src/moderation_api/types/webhook_list_response.py
new file mode 100644
index 0000000..d35726f
--- /dev/null
+++ b/src/moderation_api/types/webhook_list_response.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["WebhookListResponse", "WebhookListResponseItem"]
+
+
+class WebhookListResponseItem(BaseModel):
+ id: str
+ """The ID of the webhook."""
+
+ created_at: str = FieldInfo(alias="createdAt")
+ """The date the webhook was created."""
+
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ] = FieldInfo(alias="eventTypes")
+ """Event types this webhook subscribes to.
+
+ Empty for legacy v1 webhooks, which subscribe via their single deprecated `type`
+ instead.
+ """
+
+ name: str
+ """The webhook's name."""
+
+ payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion")
+ """Payload envelope version.
+
+ V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only
+ via this API.
+ """
+
+ url: str
+ """The URL we call when a subscribed event occurs."""
+
+ description: Optional[str] = None
+ """The webhook's description."""
+
+
+WebhookListResponse: TypeAlias = List[WebhookListResponseItem]
diff --git a/src/moderation_api/types/webhook_retrieve_response.py b/src/moderation_api/types/webhook_retrieve_response.py
new file mode 100644
index 0000000..efde9e3
--- /dev/null
+++ b/src/moderation_api/types/webhook_retrieve_response.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["WebhookRetrieveResponse"]
+
+
+class WebhookRetrieveResponse(BaseModel):
+ id: str
+ """The ID of the webhook."""
+
+ created_at: str = FieldInfo(alias="createdAt")
+ """The date the webhook was created."""
+
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ] = FieldInfo(alias="eventTypes")
+ """Event types this webhook subscribes to.
+
+ Empty for legacy v1 webhooks, which subscribe via their single deprecated `type`
+ instead.
+ """
+
+ name: str
+ """The webhook's name."""
+
+ payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion")
+ """Payload envelope version.
+
+ V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only
+ via this API.
+ """
+
+ url: str
+ """The URL we call when a subscribed event occurs."""
+
+ description: Optional[str] = None
+ """The webhook's description."""
diff --git a/src/moderation_api/types/webhook_secret_retrieve_response.py b/src/moderation_api/types/webhook_secret_retrieve_response.py
new file mode 100644
index 0000000..1b247eb
--- /dev/null
+++ b/src/moderation_api/types/webhook_secret_retrieve_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["WebhookSecretRetrieveResponse"]
+
+
+class WebhookSecretRetrieveResponse(BaseModel):
+ secret: str
+ """The signing secret for this project.
+
+ Every webhook delivery is signed with HMAC-SHA256 over the raw JSON body,
+ hex-encoded in the `modapi-signature` header.
+ """
diff --git a/src/moderation_api/types/webhook_update_params.py b/src/moderation_api/types/webhook_update_params.py
new file mode 100644
index 0000000..ae25311
--- /dev/null
+++ b/src/moderation_api/types/webhook_update_params.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["WebhookUpdateParams"]
+
+
+class WebhookUpdateParams(TypedDict, total=False):
+ description: Optional[str]
+ """The webhook's description"""
+
+ event_types: Annotated[
+ List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ],
+ PropertyInfo(alias="eventTypes"),
+ ]
+ """Event types this webhook subscribes to.
+
+ One webhook URL receives all events you list here.
+ """
+
+ name: str
+ """The webhook's name, used to identify it in the dashboard"""
+
+ url: str
+ """The webhook's URL. We'll call this URL when an event occurs."""
diff --git a/src/moderation_api/types/webhook_update_response.py b/src/moderation_api/types/webhook_update_response.py
new file mode 100644
index 0000000..34604df
--- /dev/null
+++ b/src/moderation_api/types/webhook_update_response.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["WebhookUpdateResponse"]
+
+
+class WebhookUpdateResponse(BaseModel):
+ id: str
+ """The ID of the webhook."""
+
+ created_at: str = FieldInfo(alias="createdAt")
+ """The date the webhook was created."""
+
+ event_types: List[
+ Literal[
+ "QUEUE_ITEM_NEW",
+ "QUEUE_ITEM_COMPLETED",
+ "QUEUE_ITEM_ACTION",
+ "QUEUE_ITEM_REJECTED",
+ "QUEUE_ITEM_ALLOWED",
+ "AUTHOR_BLOCKED",
+ "AUTHOR_UNBLOCKED",
+ "AUTHOR_SUSPENDED",
+ "AUTHOR_UPDATED",
+ "AUTHOR_TRUST_LEVEL_CHANGED",
+ "AUTHOR_ACTION",
+ ]
+ ] = FieldInfo(alias="eventTypes")
+ """Event types this webhook subscribes to.
+
+ Empty for legacy v1 webhooks, which subscribe via their single deprecated `type`
+ instead.
+ """
+
+ name: str
+ """The webhook's name."""
+
+ payload_version: Literal["V1", "V2"] = FieldInfo(alias="payloadVersion")
+ """Payload envelope version.
+
+ V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only
+ via this API.
+ """
+
+ url: str
+ """The URL we call when a subscribed event occurs."""
+
+ description: Optional[str] = None
+ """The webhook's description."""
diff --git a/tests/api_resources/test_content.py b/tests/api_resources/test_content.py
index 9f5a864..3693af5 100644
--- a/tests/api_resources/test_content.py
+++ b/tests/api_resources/test_content.py
@@ -17,6 +17,40 @@
class TestContent:
parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_stream(self, client: ModerationAPI) -> None:
+ content = client.content.stream(
+ sec_web_socket_protocol="moderationapi.v1",
+ )
+ assert content is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_stream(self, client: ModerationAPI) -> None:
+ response = client.content.with_raw_response.stream(
+ sec_web_socket_protocol="moderationapi.v1",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ content = response.parse()
+ assert content is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_stream(self, client: ModerationAPI) -> None:
+ with client.content.with_streaming_response.stream(
+ sec_web_socket_protocol="moderationapi.v1",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ content = response.parse()
+ assert content is None
+
+ assert cast(Any, response.is_closed) is True
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_submit(self, client: ModerationAPI) -> None:
@@ -98,6 +132,40 @@ class TestAsyncContent:
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_stream(self, async_client: AsyncModerationAPI) -> None:
+ content = await async_client.content.stream(
+ sec_web_socket_protocol="moderationapi.v1",
+ )
+ assert content is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_stream(self, async_client: AsyncModerationAPI) -> None:
+ response = await async_client.content.with_raw_response.stream(
+ sec_web_socket_protocol="moderationapi.v1",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ content = await response.parse()
+ assert content is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_stream(self, async_client: AsyncModerationAPI) -> None:
+ async with async_client.content.with_streaming_response.stream(
+ sec_web_socket_protocol="moderationapi.v1",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ content = await response.parse()
+ assert content is None
+
+ assert cast(Any, response.is_closed) is True
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_submit(self, async_client: AsyncModerationAPI) -> None:
diff --git a/tests/api_resources/test_webhook_secret.py b/tests/api_resources/test_webhook_secret.py
new file mode 100644
index 0000000..0d8f0fc
--- /dev/null
+++ b/tests/api_resources/test_webhook_secret.py
@@ -0,0 +1,80 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from moderation_api import ModerationAPI, AsyncModerationAPI
+from moderation_api.types import WebhookSecretRetrieveResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestWebhookSecret:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: ModerationAPI) -> None:
+ webhook_secret = client.webhook_secret.retrieve()
+ assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: ModerationAPI) -> None:
+ response = client.webhook_secret.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook_secret = response.parse()
+ assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: ModerationAPI) -> None:
+ with client.webhook_secret.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook_secret = response.parse()
+ assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncWebhookSecret:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None:
+ webhook_secret = await async_client.webhook_secret.retrieve()
+ assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None:
+ response = await async_client.webhook_secret.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook_secret = await response.parse()
+ assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None:
+ async with async_client.webhook_secret.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook_secret = await response.parse()
+ assert_matches_type(WebhookSecretRetrieveResponse, webhook_secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py
new file mode 100644
index 0000000..33ea5bd
--- /dev/null
+++ b/tests/api_resources/test_webhooks.py
@@ -0,0 +1,464 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from moderation_api import ModerationAPI, AsyncModerationAPI
+from moderation_api.types import (
+ WebhookListResponse,
+ WebhookCreateResponse,
+ WebhookDeleteResponse,
+ WebhookUpdateResponse,
+ WebhookRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestWebhooks:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: ModerationAPI) -> None:
+ webhook = client.webhooks.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ )
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: ModerationAPI) -> None:
+ webhook = client.webhooks.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ description="description",
+ )
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: ModerationAPI) -> None:
+ response = client.webhooks.with_raw_response.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: ModerationAPI) -> None:
+ with client.webhooks.with_streaming_response.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: ModerationAPI) -> None:
+ webhook = client.webhooks.retrieve(
+ "id",
+ )
+ assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: ModerationAPI) -> None:
+ response = client.webhooks.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: ModerationAPI) -> None:
+ with client.webhooks.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: ModerationAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.webhooks.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: ModerationAPI) -> None:
+ webhook = client.webhooks.update(
+ id="id",
+ )
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: ModerationAPI) -> None:
+ webhook = client.webhooks.update(
+ id="id",
+ description="description",
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ )
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: ModerationAPI) -> None:
+ response = client.webhooks.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: ModerationAPI) -> None:
+ with client.webhooks.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: ModerationAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.webhooks.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: ModerationAPI) -> None:
+ webhook = client.webhooks.list()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: ModerationAPI) -> None:
+ response = client.webhooks.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: ModerationAPI) -> None:
+ with client.webhooks.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: ModerationAPI) -> None:
+ webhook = client.webhooks.delete(
+ "id",
+ )
+ assert_matches_type(WebhookDeleteResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: ModerationAPI) -> None:
+ response = client.webhooks.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(WebhookDeleteResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: ModerationAPI) -> None:
+ with client.webhooks.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(WebhookDeleteResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_delete(self, client: ModerationAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.webhooks.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncWebhooks:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncModerationAPI) -> None:
+ webhook = await async_client.webhooks.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ )
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncModerationAPI) -> None:
+ webhook = await async_client.webhooks.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ description="description",
+ )
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncModerationAPI) -> None:
+ response = await async_client.webhooks.with_raw_response.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncModerationAPI) -> None:
+ async with async_client.webhooks.with_streaming_response.create(
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(WebhookCreateResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncModerationAPI) -> None:
+ webhook = await async_client.webhooks.retrieve(
+ "id",
+ )
+ assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncModerationAPI) -> None:
+ response = await async_client.webhooks.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncModerationAPI) -> None:
+ async with async_client.webhooks.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncModerationAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.webhooks.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncModerationAPI) -> None:
+ webhook = await async_client.webhooks.update(
+ id="id",
+ )
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncModerationAPI) -> None:
+ webhook = await async_client.webhooks.update(
+ id="id",
+ description="description",
+ event_types=["QUEUE_ITEM_NEW"],
+ name="x",
+ url="https://example.com",
+ )
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncModerationAPI) -> None:
+ response = await async_client.webhooks.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncModerationAPI) -> None:
+ async with async_client.webhooks.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(WebhookUpdateResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncModerationAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.webhooks.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncModerationAPI) -> None:
+ webhook = await async_client.webhooks.list()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncModerationAPI) -> None:
+ response = await async_client.webhooks.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncModerationAPI) -> None:
+ async with async_client.webhooks.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncModerationAPI) -> None:
+ webhook = await async_client.webhooks.delete(
+ "id",
+ )
+ assert_matches_type(WebhookDeleteResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncModerationAPI) -> None:
+ response = await async_client.webhooks.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(WebhookDeleteResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncModerationAPI) -> None:
+ async with async_client.webhooks.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(WebhookDeleteResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncModerationAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.webhooks.with_raw_response.delete(
+ "",
+ )
From 68a10d7589e7a6fa9ac8b6b9b9bddcc42b338fc8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 11:25:54 +0000
Subject: [PATCH 4/4] release: 1.35.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 8 ++++++++
pyproject.toml | 2 +-
src/moderation_api/_version.py | 2 +-
4 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 257e308..44959ac 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.34.0"
+ ".": "1.35.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b682512..8cd1a61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 1.35.0 (2026-08-20)
+
+Full Changelog: [v1.34.0...v1.35.0](https://github.com/moderation-api/sdk-python/compare/v1.34.0...v1.35.0)
+
+### Features
+
+* **api:** webhooks and stream ([4f4bf9f](https://github.com/moderation-api/sdk-python/commit/4f4bf9f2d1e2b6c5ddea270e16fd4d8f10600618))
+
## 1.34.0 (2026-08-14)
Full Changelog: [v1.33.0...v1.34.0](https://github.com/moderation-api/sdk-python/compare/v1.33.0...v1.34.0)
diff --git a/pyproject.toml b/pyproject.toml
index 6834ebd..4e756b9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "moderation_api"
-version = "1.34.0"
+version = "1.35.0"
description = "The official Python library for the moderation-api API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/moderation_api/_version.py b/src/moderation_api/_version.py
index 7ed0731..ba231c7 100644
--- a/src/moderation_api/_version.py
+++ b/src/moderation_api/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "moderation_api"
-__version__ = "1.34.0" # x-release-please-version
+__version__ = "1.35.0" # x-release-please-version