From b4e4ce8a56859a87137349e5f97ede2c8acaad25 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 07:26:59 +0000 Subject: [PATCH 01/10] chore: update mock server docs --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a02fdd..e40b567 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. ```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh From 3eddd677b69f387149336e11abe71a6143290ac4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:20:04 +0000 Subject: [PATCH 02/10] feat(api): add tenantId to send --- .stats.yml | 4 +- src/ark/resources/emails.py | 60 +++++++++++++++++++ src/ark/types/email_list_response.py | 3 + .../email_retrieve_deliveries_response.py | 3 + src/ark/types/email_retrieve_response.py | 3 + src/ark/types/email_retry_response.py | 5 ++ src/ark/types/email_send_batch_params.py | 11 ++++ src/ark/types/email_send_batch_response.py | 5 ++ src/ark/types/email_send_params.py | 11 ++++ src/ark/types/email_send_raw_params.py | 11 ++++ src/ark/types/email_send_raw_response.py | 3 + src/ark/types/email_send_response.py | 3 + tests/api_resources/test_emails.py | 6 ++ 13 files changed, 126 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c8ee129..e530c9b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 58 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-98a90852ffca49f4e26c613afff433b17023ee1f81f38ad38a5dad60a0d09881.yml -openapi_spec_hash: c6fd865dd6995df15cf9e6ada2ae791e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-06c3025bf12b191c3906b28173c9b359e24481dd2839dbf3e6dd0b80c1de3fd6.yml +openapi_spec_hash: d8f8fb1f78579997b6381d64cba4e826 config_hash: b70b11b10fc614f91f1c6f028b40780f diff --git a/src/ark/resources/emails.py b/src/ark/resources/emails.py index 77631ce..508b5e9 100644 --- a/src/ark/resources/emails.py +++ b/src/ark/resources/emails.py @@ -317,6 +317,7 @@ def send( metadata: Optional[Dict[str, str]] | Omit = omit, reply_to: Optional[str] | Omit = omit, tag: Optional[str] | Omit = omit, + tenant_id: Optional[str] | Omit = omit, text: Optional[str] | Omit = omit, idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -396,6 +397,14 @@ def send( tag: Tag for categorization and filtering (accepts null) + tenant_id: The tenant ID to send this email from. Determines which tenant's configuration + (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + text: Plain text body (accepts null, auto-generated from HTML if not provided). Maximum 5MB (5,242,880 characters). @@ -423,6 +432,7 @@ def send( "metadata": metadata, "reply_to": reply_to, "tag": tag, + "tenant_id": tenant_id, "text": text, }, email_send_params.EmailSendParams, @@ -438,6 +448,7 @@ def send_batch( *, emails: Iterable[email_send_batch_params.Email], from_: str, + tenant_id: Optional[str] | Omit = omit, idempotency_key: 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. @@ -459,6 +470,14 @@ def send_batch( Args: from_: Sender email for all messages + tenant_id: The tenant ID to send this batch from. Determines which tenant's configuration + (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -474,6 +493,7 @@ def send_batch( { "emails": emails, "from_": from_, + "tenant_id": tenant_id, }, email_send_batch_params.EmailSendBatchParams, ), @@ -490,6 +510,7 @@ def send_raw( raw_message: str, to: SequenceNotStr[str], bounce: Optional[bool] | Omit = omit, + tenant_id: 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, @@ -527,6 +548,14 @@ def send_raw( bounce: Whether this is a bounce message (accepts null) + tenant_id: The tenant ID to send this email from. Determines which tenant's configuration + (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -543,6 +572,7 @@ def send_raw( "raw_message": raw_message, "to": to, "bounce": bounce, + "tenant_id": tenant_id, }, email_send_raw_params.EmailSendRawParams, ), @@ -833,6 +863,7 @@ async def send( metadata: Optional[Dict[str, str]] | Omit = omit, reply_to: Optional[str] | Omit = omit, tag: Optional[str] | Omit = omit, + tenant_id: Optional[str] | Omit = omit, text: Optional[str] | Omit = omit, idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -912,6 +943,14 @@ async def send( tag: Tag for categorization and filtering (accepts null) + tenant_id: The tenant ID to send this email from. Determines which tenant's configuration + (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + text: Plain text body (accepts null, auto-generated from HTML if not provided). Maximum 5MB (5,242,880 characters). @@ -939,6 +978,7 @@ async def send( "metadata": metadata, "reply_to": reply_to, "tag": tag, + "tenant_id": tenant_id, "text": text, }, email_send_params.EmailSendParams, @@ -954,6 +994,7 @@ async def send_batch( *, emails: Iterable[email_send_batch_params.Email], from_: str, + tenant_id: Optional[str] | Omit = omit, idempotency_key: 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. @@ -975,6 +1016,14 @@ async def send_batch( Args: from_: Sender email for all messages + tenant_id: The tenant ID to send this batch from. Determines which tenant's configuration + (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -990,6 +1039,7 @@ async def send_batch( { "emails": emails, "from_": from_, + "tenant_id": tenant_id, }, email_send_batch_params.EmailSendBatchParams, ), @@ -1006,6 +1056,7 @@ async def send_raw( raw_message: str, to: SequenceNotStr[str], bounce: Optional[bool] | Omit = omit, + tenant_id: 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, @@ -1043,6 +1094,14 @@ async def send_raw( bounce: Whether this is a bounce message (accepts null) + tenant_id: The tenant ID to send this email from. Determines which tenant's configuration + (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1059,6 +1118,7 @@ async def send_raw( "raw_message": raw_message, "to": to, "bounce": bounce, + "tenant_id": tenant_id, }, email_send_raw_params.EmailSendRawParams, ), diff --git a/src/ark/types/email_list_response.py b/src/ark/types/email_list_response.py index dce7d39..21bd18e 100644 --- a/src/ark/types/email_list_response.py +++ b/src/ark/types/email_list_response.py @@ -30,6 +30,9 @@ class EmailListResponse(BaseModel): subject: str + tenant_id: str = FieldInfo(alias="tenantId") + """The tenant ID this email belongs to""" + timestamp: float timestamp_iso: datetime = FieldInfo(alias="timestampIso") diff --git a/src/ark/types/email_retrieve_deliveries_response.py b/src/ark/types/email_retrieve_deliveries_response.py index a923d98..1f3c1bc 100644 --- a/src/ark/types/email_retrieve_deliveries_response.py +++ b/src/ark/types/email_retrieve_deliveries_response.py @@ -170,6 +170,9 @@ class Data(BaseModel): - `bounced` - Bounced by recipient server """ + tenant_id: str = FieldInfo(alias="tenantId") + """The tenant ID this email belongs to""" + class EmailRetrieveDeliveriesResponse(BaseModel): data: Data diff --git a/src/ark/types/email_retrieve_response.py b/src/ark/types/email_retrieve_response.py index d9bc54c..c0de6b1 100644 --- a/src/ark/types/email_retrieve_response.py +++ b/src/ark/types/email_retrieve_response.py @@ -179,6 +179,9 @@ class Data(BaseModel): subject: str """Email subject line""" + tenant_id: str = FieldInfo(alias="tenantId") + """The tenant ID this email belongs to""" + timestamp: float """Unix timestamp when the email was sent""" diff --git a/src/ark/types/email_retry_response.py b/src/ark/types/email_retry_response.py index f63cc99..fe5d8ce 100644 --- a/src/ark/types/email_retry_response.py +++ b/src/ark/types/email_retry_response.py @@ -2,6 +2,8 @@ from typing_extensions import Literal +from pydantic import Field as FieldInfo + from .._models import BaseModel from .shared.api_meta import APIMeta @@ -14,6 +16,9 @@ class Data(BaseModel): message: str + tenant_id: str = FieldInfo(alias="tenantId") + """The tenant ID this email belongs to""" + class EmailRetryResponse(BaseModel): data: Data diff --git a/src/ark/types/email_send_batch_params.py b/src/ark/types/email_send_batch_params.py index ba54d41..38c9caf 100644 --- a/src/ark/types/email_send_batch_params.py +++ b/src/ark/types/email_send_batch_params.py @@ -17,6 +17,17 @@ class EmailSendBatchParams(TypedDict, total=False): from_: Required[Annotated[str, PropertyInfo(alias="from")]] """Sender email for all messages""" + tenant_id: Annotated[Optional[str], PropertyInfo(alias="tenantId")] + """The tenant ID to send this batch from. + + Determines which tenant's configuration (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + """ + idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")] diff --git a/src/ark/types/email_send_batch_response.py b/src/ark/types/email_send_batch_response.py index d32e3fc..06fe526 100644 --- a/src/ark/types/email_send_batch_response.py +++ b/src/ark/types/email_send_batch_response.py @@ -3,6 +3,8 @@ from typing import Dict, Optional from typing_extensions import Literal +from pydantic import Field as FieldInfo + from .._models import BaseModel from .shared.api_meta import APIMeta @@ -24,6 +26,9 @@ class Data(BaseModel): messages: Dict[str, DataMessages] """Map of recipient email to message info""" + tenant_id: str = FieldInfo(alias="tenantId") + """The tenant ID this batch was sent from""" + total: int """Total emails in the batch""" diff --git a/src/ark/types/email_send_params.py b/src/ark/types/email_send_params.py index 0363339..af3e94f 100644 --- a/src/ark/types/email_send_params.py +++ b/src/ark/types/email_send_params.py @@ -79,6 +79,17 @@ class EmailSendParams(TypedDict, total=False): tag: Optional[str] """Tag for categorization and filtering (accepts null)""" + tenant_id: Annotated[Optional[str], PropertyInfo(alias="tenantId")] + """The tenant ID to send this email from. + + Determines which tenant's configuration (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + """ + text: Optional[str] """ Plain text body (accepts null, auto-generated from HTML if not provided). diff --git a/src/ark/types/email_send_raw_params.py b/src/ark/types/email_send_raw_params.py index d957f74..56c038b 100644 --- a/src/ark/types/email_send_raw_params.py +++ b/src/ark/types/email_send_raw_params.py @@ -37,3 +37,14 @@ class EmailSendRawParams(TypedDict, total=False): bounce: Optional[bool] """Whether this is a bounce message (accepts null)""" + + tenant_id: Annotated[Optional[str], PropertyInfo(alias="tenantId")] + """The tenant ID to send this email from. + + Determines which tenant's configuration (domains, webhooks, tracking) is used. + + - If your API key is scoped to a specific tenant, this must match that tenant or + be omitted. + - If your API key is org-level, specify the tenant to send from. + - If omitted, the organization's default tenant is used. + """ diff --git a/src/ark/types/email_send_raw_response.py b/src/ark/types/email_send_raw_response.py index 46e7c96..80a61fb 100644 --- a/src/ark/types/email_send_raw_response.py +++ b/src/ark/types/email_send_raw_response.py @@ -18,6 +18,9 @@ class Data(BaseModel): status: Literal["pending", "sent"] """Current delivery status""" + tenant_id: str = FieldInfo(alias="tenantId") + """The tenant ID this email was sent from""" + to: List[str] """List of recipient addresses""" diff --git a/src/ark/types/email_send_response.py b/src/ark/types/email_send_response.py index cb7d814..7c54d65 100644 --- a/src/ark/types/email_send_response.py +++ b/src/ark/types/email_send_response.py @@ -18,6 +18,9 @@ class Data(BaseModel): status: Literal["pending", "sent"] """Current delivery status""" + tenant_id: str = FieldInfo(alias="tenantId") + """The tenant ID this email was sent from""" + to: List[str] """List of recipient addresses""" diff --git a/tests/api_resources/test_emails.py b/tests/api_resources/test_emails.py index 0e37b72..daa4744 100644 --- a/tests/api_resources/test_emails.py +++ b/tests/api_resources/test_emails.py @@ -219,6 +219,7 @@ def test_method_send_with_all_params(self, client: Ark) -> None: }, reply_to="dev@stainless.com", tag="tag", + tenant_id="cm6abc123def456", text="text", idempotency_key="user_123_order_456", ) @@ -299,6 +300,7 @@ def test_method_send_batch_with_all_params(self, client: Ark) -> None: }, ], from_="notifications@myapp.com", + tenant_id="cm6abc123def456", idempotency_key="user_123_order_456", ) assert_matches_type(EmailSendBatchResponse, email, path=["response"]) @@ -363,6 +365,7 @@ def test_method_send_raw_with_all_params(self, client: Ark) -> None: raw_message="x", to=["user@example.com"], bounce=True, + tenant_id="cm6abc123def456", ) assert_matches_type(EmailSendRawResponse, email, path=["response"]) @@ -593,6 +596,7 @@ async def test_method_send_with_all_params(self, async_client: AsyncArk) -> None }, reply_to="dev@stainless.com", tag="tag", + tenant_id="cm6abc123def456", text="text", idempotency_key="user_123_order_456", ) @@ -673,6 +677,7 @@ async def test_method_send_batch_with_all_params(self, async_client: AsyncArk) - }, ], from_="notifications@myapp.com", + tenant_id="cm6abc123def456", idempotency_key="user_123_order_456", ) assert_matches_type(EmailSendBatchResponse, email, path=["response"]) @@ -737,6 +742,7 @@ async def test_method_send_raw_with_all_params(self, async_client: AsyncArk) -> raw_message="x", to=["user@example.com"], bounce=True, + tenant_id="cm6abc123def456", ) assert_matches_type(EmailSendRawResponse, email, path=["response"]) From fdc5e91d4774006a051e8a289bbd1b3c7eec1b8c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:39:41 +0000 Subject: [PATCH 03/10] chore(internal): add request options to SSE classes --- src/ark/_response.py | 3 +++ src/ark/_streaming.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ark/_response.py b/src/ark/_response.py index 5fd3b90..6125f53 100644 --- a/src/ark/_response.py +++ b/src/ark/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/ark/_streaming.py b/src/ark/_streaming.py index a271a69..cd236ca 100644 --- a/src/ark/_streaming.py +++ b/src/ark/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import Ark, AsyncArk + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Ark, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncArk, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() From 709aff401224092c3e5059559951c8bc82c59866 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:47:57 +0000 Subject: [PATCH 04/10] chore(internal): make `test_proxy_environment_variables` more resilient --- tests/test_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 73b2133..9e2b6b2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -951,6 +951,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultHttpxClient() @@ -1859,6 +1861,8 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultAsyncHttpxClient() From df5b8639e08e14c7a64e51081f60c41d0450617b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:32:19 +0000 Subject: [PATCH 05/10] chore(internal): make `test_proxy_environment_variables` more resilient to env --- tests/test_client.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 9e2b6b2..ead5154 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -951,8 +951,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1861,8 +1867,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient() From e7115edad4dd96f45c7b87f76b00792b8d096647 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:21:20 +0000 Subject: [PATCH 06/10] chore(ci): bump uv version --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1964062..0fbfd05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: '0.9.13' + version: '0.10.2' - name: Install dependencies run: uv sync --all-extras @@ -46,7 +46,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: '0.9.13' + version: '0.10.2' - name: Install dependencies run: uv sync --all-extras @@ -80,7 +80,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: '0.9.13' + version: '0.10.2' - name: Bootstrap run: ./scripts/bootstrap From a6cc237ae03c83da16f8cc279dac4fe5e91e0816 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:54:11 +0000 Subject: [PATCH 07/10] chore(internal): codegen related update --- src/ark/_client.py | 438 +++++++++++++ src/ark/resources/emails.py | 20 + src/ark/resources/limits.py | 32 + src/ark/resources/logs.py | 42 ++ src/ark/resources/tenants/domains.py | 28 + src/ark/resources/tenants/suppressions.py | 24 + src/ark/resources/tenants/tenants.py | 710 ++++++++++++++++++++++ src/ark/resources/tenants/tracking.py | 42 ++ src/ark/resources/tenants/usage.py | 36 ++ src/ark/resources/tenants/webhooks.py | 108 ++++ src/ark/resources/usage.py | 36 ++ 11 files changed, 1516 insertions(+) diff --git a/src/ark/_client.py b/src/ark/_client.py index 40de787..d682b1b 100644 --- a/src/ark/_client.py +++ b/src/ark/_client.py @@ -99,30 +99,103 @@ def __init__( @cached_property def emails(self) -> EmailsResource: + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ from .resources.emails import EmailsResource return EmailsResource(self) @cached_property def logs(self) -> LogsResource: + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ from .resources.logs import LogsResource return LogsResource(self) @cached_property def usage(self) -> UsageResource: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ from .resources.usage import UsageResource return UsageResource(self) @cached_property def limits(self) -> LimitsResource: + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ from .resources.limits import LimitsResource return LimitsResource(self) @cached_property def tenants(self) -> TenantsResource: + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ from .resources.tenants import TenantsResource return TenantsResource(self) @@ -303,30 +376,103 @@ def __init__( @cached_property def emails(self) -> AsyncEmailsResource: + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ from .resources.emails import AsyncEmailsResource return AsyncEmailsResource(self) @cached_property def logs(self) -> AsyncLogsResource: + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ from .resources.logs import AsyncLogsResource return AsyncLogsResource(self) @cached_property def usage(self) -> AsyncUsageResource: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ from .resources.usage import AsyncUsageResource return AsyncUsageResource(self) @cached_property def limits(self) -> AsyncLimitsResource: + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ from .resources.limits import AsyncLimitsResource return AsyncLimitsResource(self) @cached_property def tenants(self) -> AsyncTenantsResource: + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ from .resources.tenants import AsyncTenantsResource return AsyncTenantsResource(self) @@ -458,30 +604,103 @@ def __init__(self, client: Ark) -> None: @cached_property def emails(self) -> emails.EmailsResourceWithRawResponse: + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ from .resources.emails import EmailsResourceWithRawResponse return EmailsResourceWithRawResponse(self._client.emails) @cached_property def logs(self) -> logs.LogsResourceWithRawResponse: + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ from .resources.logs import LogsResourceWithRawResponse return LogsResourceWithRawResponse(self._client.logs) @cached_property def usage(self) -> usage.UsageResourceWithRawResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ from .resources.usage import UsageResourceWithRawResponse return UsageResourceWithRawResponse(self._client.usage) @cached_property def limits(self) -> limits.LimitsResourceWithRawResponse: + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ from .resources.limits import LimitsResourceWithRawResponse return LimitsResourceWithRawResponse(self._client.limits) @cached_property def tenants(self) -> tenants.TenantsResourceWithRawResponse: + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ from .resources.tenants import TenantsResourceWithRawResponse return TenantsResourceWithRawResponse(self._client.tenants) @@ -501,30 +720,103 @@ def __init__(self, client: AsyncArk) -> None: @cached_property def emails(self) -> emails.AsyncEmailsResourceWithRawResponse: + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ from .resources.emails import AsyncEmailsResourceWithRawResponse return AsyncEmailsResourceWithRawResponse(self._client.emails) @cached_property def logs(self) -> logs.AsyncLogsResourceWithRawResponse: + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ from .resources.logs import AsyncLogsResourceWithRawResponse return AsyncLogsResourceWithRawResponse(self._client.logs) @cached_property def usage(self) -> usage.AsyncUsageResourceWithRawResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ from .resources.usage import AsyncUsageResourceWithRawResponse return AsyncUsageResourceWithRawResponse(self._client.usage) @cached_property def limits(self) -> limits.AsyncLimitsResourceWithRawResponse: + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ from .resources.limits import AsyncLimitsResourceWithRawResponse return AsyncLimitsResourceWithRawResponse(self._client.limits) @cached_property def tenants(self) -> tenants.AsyncTenantsResourceWithRawResponse: + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ from .resources.tenants import AsyncTenantsResourceWithRawResponse return AsyncTenantsResourceWithRawResponse(self._client.tenants) @@ -544,30 +836,103 @@ def __init__(self, client: Ark) -> None: @cached_property def emails(self) -> emails.EmailsResourceWithStreamingResponse: + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ from .resources.emails import EmailsResourceWithStreamingResponse return EmailsResourceWithStreamingResponse(self._client.emails) @cached_property def logs(self) -> logs.LogsResourceWithStreamingResponse: + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ from .resources.logs import LogsResourceWithStreamingResponse return LogsResourceWithStreamingResponse(self._client.logs) @cached_property def usage(self) -> usage.UsageResourceWithStreamingResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ from .resources.usage import UsageResourceWithStreamingResponse return UsageResourceWithStreamingResponse(self._client.usage) @cached_property def limits(self) -> limits.LimitsResourceWithStreamingResponse: + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ from .resources.limits import LimitsResourceWithStreamingResponse return LimitsResourceWithStreamingResponse(self._client.limits) @cached_property def tenants(self) -> tenants.TenantsResourceWithStreamingResponse: + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ from .resources.tenants import TenantsResourceWithStreamingResponse return TenantsResourceWithStreamingResponse(self._client.tenants) @@ -587,30 +952,103 @@ def __init__(self, client: AsyncArk) -> None: @cached_property def emails(self) -> emails.AsyncEmailsResourceWithStreamingResponse: + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ from .resources.emails import AsyncEmailsResourceWithStreamingResponse return AsyncEmailsResourceWithStreamingResponse(self._client.emails) @cached_property def logs(self) -> logs.AsyncLogsResourceWithStreamingResponse: + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ from .resources.logs import AsyncLogsResourceWithStreamingResponse return AsyncLogsResourceWithStreamingResponse(self._client.logs) @cached_property def usage(self) -> usage.AsyncUsageResourceWithStreamingResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ from .resources.usage import AsyncUsageResourceWithStreamingResponse return AsyncUsageResourceWithStreamingResponse(self._client.usage) @cached_property def limits(self) -> limits.AsyncLimitsResourceWithStreamingResponse: + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ from .resources.limits import AsyncLimitsResourceWithStreamingResponse return AsyncLimitsResourceWithStreamingResponse(self._client.limits) @cached_property def tenants(self) -> tenants.AsyncTenantsResourceWithStreamingResponse: + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ from .resources.tenants import AsyncTenantsResourceWithStreamingResponse return AsyncTenantsResourceWithStreamingResponse(self._client.tenants) diff --git a/src/ark/resources/emails.py b/src/ark/resources/emails.py index 508b5e9..1883149 100644 --- a/src/ark/resources/emails.py +++ b/src/ark/resources/emails.py @@ -38,6 +38,16 @@ class EmailsResource(SyncAPIResource): + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ + @cached_property def with_raw_response(self) -> EmailsResourceWithRawResponse: """ @@ -584,6 +594,16 @@ def send_raw( class AsyncEmailsResource(AsyncAPIResource): + """Send and manage email messages. + + **Quick Reference:** + - `POST /emails` - Send a single email + - `POST /emails/batch` - Send up to 100 emails + - `GET /emails/{emailId}` - Get email status and details + - `GET /emails` - List sent emails + - `POST /emails/{emailId}/retry` - Retry failed delivery + """ + @cached_property def with_raw_response(self) -> AsyncEmailsResourceWithRawResponse: """ diff --git a/src/ark/resources/limits.py b/src/ark/resources/limits.py index 8dd9db6..19a468b 100644 --- a/src/ark/resources/limits.py +++ b/src/ark/resources/limits.py @@ -20,6 +20,22 @@ class LimitsResource(SyncAPIResource): + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ + @cached_property def with_raw_response(self) -> LimitsResourceWithRawResponse: """ @@ -78,6 +94,22 @@ def retrieve( class AsyncLimitsResource(AsyncAPIResource): + """Check account rate limits and send limits. + + The limits endpoint returns current status for operational limits: + - **Rate limit:** API requests per second (currently 10/sec) + - **Send limit:** Emails per hour (default 100/hour for new accounts) + - **Billing:** Credit balance and auto-recharge configuration + + **AI Integration Note:** This endpoint is designed for AI agents and MCP servers + to understand account constraints before taking actions. Call this endpoint + first when planning batch operations to avoid hitting limits unexpectedly. + + **Quick Reference:** + - `GET /limits` - Get current rate limits and send limits + - `GET /usage` - (Deprecated) Use `/limits` instead + """ + @cached_property def with_raw_response(self) -> AsyncLimitsResourceWithRawResponse: """ diff --git a/src/ark/resources/logs.py b/src/ark/resources/logs.py index 573538d..d4c86be 100644 --- a/src/ark/resources/logs.py +++ b/src/ark/resources/logs.py @@ -28,6 +28,27 @@ class LogsResource(SyncAPIResource): + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ + @cached_property def with_raw_response(self) -> LogsResourceWithRawResponse: """ @@ -198,6 +219,27 @@ def list( class AsyncLogsResource(AsyncAPIResource): + """Access API request logs for debugging and monitoring. + + Every API request is logged with details including: + - Request method, path, and endpoint + - Response status code and duration + - Error details (code, message) for failed requests + - SDK information (name, version) + - Rate limit state at time of request + - Request and response bodies (for single log retrieval) + + **Retention:** Logs are retained for 90 days. + + **Body storage:** Request and response bodies are stored encrypted + and truncated at 25KB. Bodies are only returned when retrieving + a single log entry. + + **Quick Reference:** + - `GET /logs` - List API request logs with filters + - `GET /logs/{requestId}` - Get full details including request/response bodies + """ + @cached_property def with_raw_response(self) -> AsyncLogsResourceWithRawResponse: """ diff --git a/src/ark/resources/tenants/domains.py b/src/ark/resources/tenants/domains.py index e560458..d941595 100644 --- a/src/ark/resources/tenants/domains.py +++ b/src/ark/resources/tenants/domains.py @@ -26,6 +26,20 @@ class DomainsResource(SyncAPIResource): + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ + @cached_property def with_raw_response(self) -> DomainsResourceWithRawResponse: """ @@ -246,6 +260,20 @@ def verify( class AsyncDomainsResource(AsyncAPIResource): + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ + @cached_property def with_raw_response(self) -> AsyncDomainsResourceWithRawResponse: """ diff --git a/src/ark/resources/tenants/suppressions.py b/src/ark/resources/tenants/suppressions.py index 070b7e1..f0f784b 100644 --- a/src/ark/resources/tenants/suppressions.py +++ b/src/ark/resources/tenants/suppressions.py @@ -28,6 +28,18 @@ class SuppressionsResource(SyncAPIResource): + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ + @cached_property def with_raw_response(self) -> SuppressionsResourceWithRawResponse: """ @@ -219,6 +231,18 @@ def delete( class AsyncSuppressionsResource(AsyncAPIResource): + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ + @cached_property def with_raw_response(self) -> AsyncSuppressionsResourceWithRawResponse: """ diff --git a/src/ark/resources/tenants/tenants.py b/src/ark/resources/tenants/tenants.py index 684cf1a..eaf252b 100644 --- a/src/ark/resources/tenants/tenants.py +++ b/src/ark/resources/tenants/tenants.py @@ -78,28 +78,155 @@ class TenantsResource(SyncAPIResource): + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ + @cached_property def credentials(self) -> CredentialsResource: return CredentialsResource(self._client) @cached_property def domains(self) -> DomainsResource: + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ return DomainsResource(self._client) @cached_property def suppressions(self) -> SuppressionsResource: + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ return SuppressionsResource(self._client) @cached_property def webhooks(self) -> WebhooksResource: + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ return WebhooksResource(self._client) @cached_property def tracking(self) -> TrackingResource: + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ return TrackingResource(self._client) @cached_property def usage(self) -> UsageResource: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ return UsageResource(self._client) @cached_property @@ -356,28 +483,155 @@ def delete( class AsyncTenantsResource(AsyncAPIResource): + """Manage tenants (your customers). + + Create a tenant for each of your customers to track their email sending separately. + Store the tenant `id` in your database and use `metadata` for any custom data. + + **Quick Reference:** + - `POST /tenants` - Create a new tenant + - `GET /tenants` - List all tenants (paginated) + - `GET /tenants/{id}` - Get tenant details + - `PATCH /tenants/{id}` - Update tenant name, metadata, or status + - `DELETE /tenants/{id}` - Delete a tenant + """ + @cached_property def credentials(self) -> AsyncCredentialsResource: return AsyncCredentialsResource(self._client) @cached_property def domains(self) -> AsyncDomainsResource: + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ return AsyncDomainsResource(self._client) @cached_property def suppressions(self) -> AsyncSuppressionsResource: + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ return AsyncSuppressionsResource(self._client) @cached_property def webhooks(self) -> AsyncWebhooksResource: + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ return AsyncWebhooksResource(self._client) @cached_property def tracking(self) -> AsyncTrackingResource: + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ return AsyncTrackingResource(self._client) @cached_property def usage(self) -> AsyncUsageResource: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ return AsyncUsageResource(self._client) @cached_property @@ -659,22 +913,136 @@ def credentials(self) -> CredentialsResourceWithRawResponse: @cached_property def domains(self) -> DomainsResourceWithRawResponse: + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ return DomainsResourceWithRawResponse(self._tenants.domains) @cached_property def suppressions(self) -> SuppressionsResourceWithRawResponse: + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ return SuppressionsResourceWithRawResponse(self._tenants.suppressions) @cached_property def webhooks(self) -> WebhooksResourceWithRawResponse: + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ return WebhooksResourceWithRawResponse(self._tenants.webhooks) @cached_property def tracking(self) -> TrackingResourceWithRawResponse: + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ return TrackingResourceWithRawResponse(self._tenants.tracking) @cached_property def usage(self) -> UsageResourceWithRawResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ return UsageResourceWithRawResponse(self._tenants.usage) @@ -704,22 +1072,136 @@ def credentials(self) -> AsyncCredentialsResourceWithRawResponse: @cached_property def domains(self) -> AsyncDomainsResourceWithRawResponse: + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ return AsyncDomainsResourceWithRawResponse(self._tenants.domains) @cached_property def suppressions(self) -> AsyncSuppressionsResourceWithRawResponse: + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ return AsyncSuppressionsResourceWithRawResponse(self._tenants.suppressions) @cached_property def webhooks(self) -> AsyncWebhooksResourceWithRawResponse: + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ return AsyncWebhooksResourceWithRawResponse(self._tenants.webhooks) @cached_property def tracking(self) -> AsyncTrackingResourceWithRawResponse: + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ return AsyncTrackingResourceWithRawResponse(self._tenants.tracking) @cached_property def usage(self) -> AsyncUsageResourceWithRawResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ return AsyncUsageResourceWithRawResponse(self._tenants.usage) @@ -749,22 +1231,136 @@ def credentials(self) -> CredentialsResourceWithStreamingResponse: @cached_property def domains(self) -> DomainsResourceWithStreamingResponse: + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ return DomainsResourceWithStreamingResponse(self._tenants.domains) @cached_property def suppressions(self) -> SuppressionsResourceWithStreamingResponse: + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ return SuppressionsResourceWithStreamingResponse(self._tenants.suppressions) @cached_property def webhooks(self) -> WebhooksResourceWithStreamingResponse: + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ return WebhooksResourceWithStreamingResponse(self._tenants.webhooks) @cached_property def tracking(self) -> TrackingResourceWithStreamingResponse: + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ return TrackingResourceWithStreamingResponse(self._tenants.tracking) @cached_property def usage(self) -> UsageResourceWithStreamingResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ return UsageResourceWithStreamingResponse(self._tenants.usage) @@ -794,20 +1390,134 @@ def credentials(self) -> AsyncCredentialsResourceWithStreamingResponse: @cached_property def domains(self) -> AsyncDomainsResourceWithStreamingResponse: + """Manage sending domains. + + Before you can send emails, you need to: + 1. Add a domain + 2. Configure DNS records (SPF, DKIM, Return Path) + 3. Verify the domain + + **Quick Reference:** + - `POST /domains` - Add a new domain + - `GET /domains` - List all domains + - `POST /domains/{id}/verify` - Check DNS and verify domain + - `DELETE /domains/{id}` - Remove a domain + """ return AsyncDomainsResourceWithStreamingResponse(self._tenants.domains) @cached_property def suppressions(self) -> AsyncSuppressionsResourceWithStreamingResponse: + """Manage the suppression list. + + Suppressed email addresses will not receive any emails. Addresses are + automatically suppressed when they hard bounce or file spam complaints. + + **Quick Reference:** + - `GET /suppressions` - List suppressed addresses + - `POST /suppressions` - Add to suppression list + - `DELETE /suppressions/{email}` - Remove from suppression list + - `GET /suppressions/{email}` - Check if address is suppressed + """ return AsyncSuppressionsResourceWithStreamingResponse(self._tenants.suppressions) @cached_property def webhooks(self) -> AsyncWebhooksResourceWithStreamingResponse: + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ return AsyncWebhooksResourceWithStreamingResponse(self._tenants.webhooks) @cached_property def tracking(self) -> AsyncTrackingResourceWithStreamingResponse: + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ return AsyncTrackingResourceWithStreamingResponse(self._tenants.tracking) @cached_property def usage(self) -> AsyncUsageResourceWithStreamingResponse: + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ return AsyncUsageResourceWithStreamingResponse(self._tenants.usage) diff --git a/src/ark/resources/tenants/tracking.py b/src/ark/resources/tenants/tracking.py index 995292f..549e84e 100644 --- a/src/ark/resources/tenants/tracking.py +++ b/src/ark/resources/tenants/tracking.py @@ -29,6 +29,27 @@ class TrackingResource(SyncAPIResource): + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ + @cached_property def with_raw_response(self) -> TrackingResourceWithRawResponse: """ @@ -323,6 +344,27 @@ def verify( class AsyncTrackingResource(AsyncAPIResource): + """Manage track domains for open and click tracking. + + Track domains enable you to track when recipients: + - Open your emails (tracking pixel) + - Click links in your emails + + **Setup Process:** + 1. Create a track domain with `POST /tracking` + 2. Add the CNAME record to your DNS + 3. Verify DNS with `POST /tracking/{id}/verify` + 4. Track domain is ready when `dnsOk` is true + + **Quick Reference:** + - `POST /tracking` - Create a new track domain + - `GET /tracking` - List all track domains + - `GET /tracking/{id}` - Get track domain details + - `POST /tracking/{id}/verify` - Verify DNS configuration + - `PATCH /tracking/{id}` - Enable/disable tracking features + - `DELETE /tracking/{id}` - Remove a track domain + """ + @cached_property def with_raw_response(self) -> AsyncTrackingResourceWithRawResponse: """ diff --git a/src/ark/resources/tenants/usage.py b/src/ark/resources/tenants/usage.py index ce82b19..c7f9276 100644 --- a/src/ark/resources/tenants/usage.py +++ b/src/ark/resources/tenants/usage.py @@ -25,6 +25,24 @@ class UsageResource(SyncAPIResource): + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ + @cached_property def with_raw_response(self) -> UsageResourceWithRawResponse: """ @@ -190,6 +208,24 @@ def retrieve_timeseries( class AsyncUsageResource(AsyncAPIResource): + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ + @cached_property def with_raw_response(self) -> AsyncUsageResourceWithRawResponse: """ diff --git a/src/ark/resources/tenants/webhooks.py b/src/ark/resources/tenants/webhooks.py index ee3a683..f1f7cd5 100644 --- a/src/ark/resources/tenants/webhooks.py +++ b/src/ark/resources/tenants/webhooks.py @@ -38,6 +38,60 @@ class WebhooksResource(SyncAPIResource): + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ + @cached_property def with_raw_response(self) -> WebhooksResourceWithRawResponse: """ @@ -563,6 +617,60 @@ def test( class AsyncWebhooksResource(AsyncAPIResource): + """Configure webhook endpoints for real-time notifications. + + Webhooks notify your application when email events occur: + - Email delivered, bounced, or failed + - Email opened or link clicked + - Spam complaint received + + **Quick Reference:** + - `POST /webhooks` - Create a webhook endpoint + - `GET /webhooks` - List all webhooks + - `POST /webhooks/{id}/test` - Test a webhook with sample data + - `PATCH /webhooks/{id}` - Update webhook configuration + - `DELETE /webhooks/{id}` - Remove a webhook + - `GET /webhooks/{id}/deliveries` - List delivery attempts + - `GET /webhooks/{id}/deliveries/{deliveryId}` - Get delivery details + - `POST /webhooks/{id}/deliveries/{deliveryId}/replay` - Replay a delivery + + ## Webhook Signatures + + All webhooks are cryptographically signed using RSA-SHA256 for security. + Each webhook request includes: + + | Header | Description | + |--------|-------------| + | `X-Ark-Signature` | Base64-encoded RSA-SHA256 signature of the request body | + | `X-Ark-Signature-KID` | Key ID identifying which public key was used | + + Verify signatures by fetching the public key from: + ``` + GET https://mail.arkhq.io/.well-known/jwks.json + ``` + + ```javascript + const crypto = require('crypto'); + + async function verifyWebhook(payload, signatureBase64, publicKey) { + const signature = Buffer.from(signatureBase64, 'base64'); + const verifier = crypto.createVerify('RSA-SHA256'); + verifier.update(payload); + return verifier.verify(publicKey, signature); + } + + // In your webhook handler: + const isValid = await verifyWebhook( + rawBody, + req.headers['x-ark-signature'], + cachedPublicKey + ); + ``` + + **Important:** Always verify signatures before processing webhook data. + See the [Webhook Integration Guide](/guides/webhook-integration) for complete examples. + """ + @cached_property def with_raw_response(self) -> AsyncWebhooksResourceWithRawResponse: """ diff --git a/src/ark/resources/usage.py b/src/ark/resources/usage.py index 9a38220..05a4493 100644 --- a/src/ark/resources/usage.py +++ b/src/ark/resources/usage.py @@ -27,6 +27,24 @@ class UsageResource(SyncAPIResource): + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ + @cached_property def with_raw_response(self) -> UsageResourceWithRawResponse: """ @@ -318,6 +336,24 @@ def list_tenants( class AsyncUsageResource(AsyncAPIResource): + """Per-tenant usage analytics and bulk reporting. + + Track email sending statistics for each tenant to power billing, dashboards, and monitoring. + + **Single Tenant Usage:** + - `GET /tenants/{id}/usage` - Get usage stats for a specific tenant + - `GET /tenants/{id}/usage/timeseries` - Get time-bucketed data for charts + + **Bulk Usage:** + - `GET /usage/tenants` - Get usage for all tenants (paginated, sortable) + - `GET /usage/export` - Export usage data as CSV, JSONL, or JSON + + **Period Formats:** + - Shortcuts: `today`, `yesterday`, `this_month`, `last_month`, `last_7_days`, `last_30_days` + - Month: `2024-01` + - Date range: `2024-01-01..2024-01-15` + """ + @cached_property def with_raw_response(self) -> AsyncUsageResourceWithRawResponse: """ From e9620f619e9f8bafc0d3fdc2b070cb4e6f5454b3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:05:37 +0000 Subject: [PATCH 08/10] chore(test): do not count install time for mock server timeout --- scripts/mock | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/mock b/scripts/mock index 0b28f6e..bcf3b39 100755 --- a/scripts/mock +++ b/scripts/mock @@ -21,11 +21,22 @@ echo "==> Starting mock server with URL ${URL}" # Run prism mock on the given spec if [ "$1" == "--daemon" ]; then + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism --version + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - # Wait for server to come online + # Wait for server to come online (max 30s) echo -n "Waiting for server" + attempts=0 while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Prism server to start" + cat .prism.log + exit 1 + fi echo -n "." sleep 0.1 done From 5ad9ccb223dc029f721420b94bb87a7a7207bdb3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:00:54 +0000 Subject: [PATCH 09/10] chore(ci): skip uploading artifacts on stainless-internal branches --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fbfd05..70484f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,14 +55,18 @@ jobs: run: uv build - name: Get GitHub OIDC Token - if: github.repository == 'stainless-sdks/ark-python' + if: |- + github.repository == 'stainless-sdks/ark-python' && + !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball - if: github.repository == 'stainless-sdks/ark-python' + if: |- + github.repository == 'stainless-sdks/ark-python' && + !startsWith(github.ref, 'refs/heads/stl/') env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} From 2a5dd32ac9fe7b1a89a4381d3785e8b924af0831 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:18:47 +0000 Subject: [PATCH 10/10] release: 0.19.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ pyproject.toml | 2 +- src/ark/_version.py | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d661066..e756293 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.18.1" + ".": "0.19.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a755aaa..a45d4d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.19.0 (2026-03-07) + +Full Changelog: [v0.18.1...v0.19.0](https://github.com/ArkHQ-io/ark-python/compare/v0.18.1...v0.19.0) + +### Features + +* **api:** add tenantId to send ([3eddd67](https://github.com/ArkHQ-io/ark-python/commit/3eddd677b69f387149336e11abe71a6143290ac4)) + + +### Chores + +* **ci:** bump uv version ([e7115ed](https://github.com/ArkHQ-io/ark-python/commit/e7115edad4dd96f45c7b87f76b00792b8d096647)) +* **ci:** skip uploading artifacts on stainless-internal branches ([5ad9ccb](https://github.com/ArkHQ-io/ark-python/commit/5ad9ccb223dc029f721420b94bb87a7a7207bdb3)) +* **internal:** add request options to SSE classes ([fdc5e91](https://github.com/ArkHQ-io/ark-python/commit/fdc5e91d4774006a051e8a289bbd1b3c7eec1b8c)) +* **internal:** codegen related update ([a6cc237](https://github.com/ArkHQ-io/ark-python/commit/a6cc237ae03c83da16f8cc279dac4fe5e91e0816)) +* **internal:** make `test_proxy_environment_variables` more resilient ([709aff4](https://github.com/ArkHQ-io/ark-python/commit/709aff401224092c3e5059559951c8bc82c59866)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([df5b863](https://github.com/ArkHQ-io/ark-python/commit/df5b8639e08e14c7a64e51081f60c41d0450617b)) +* **test:** do not count install time for mock server timeout ([e9620f6](https://github.com/ArkHQ-io/ark-python/commit/e9620f619e9f8bafc0d3fdc2b070cb4e6f5454b3)) +* update mock server docs ([b4e4ce8](https://github.com/ArkHQ-io/ark-python/commit/b4e4ce8a56859a87137349e5f97ede2c8acaad25)) + ## 0.18.1 (2026-02-18) Full Changelog: [v0.18.0...v0.18.1](https://github.com/ArkHQ-io/ark-python/compare/v0.18.0...v0.18.1) diff --git a/pyproject.toml b/pyproject.toml index 0adad94..d762cba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ark-email" -version = "0.18.1" +version = "0.19.0" description = "The official Python library for the ark API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/ark/_version.py b/src/ark/_version.py index 39f8f2d..a626377 100644 --- a/src/ark/_version.py +++ b/src/ark/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "ark" -__version__ = "0.18.1" # x-release-please-version +__version__ = "0.19.0" # x-release-please-version