From bd3a602618afa61ac40af3caed3e60f6e63229ed Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 15 Dec 2025 23:10:33 +0000
Subject: [PATCH 01/15] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index f9df407d..72cccfe2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-fc4ab722e6762cc69d533f57bea0d70b00e44a30c4ad8144e14ff70a1170ec7c.yml
openapi_spec_hash: 2533ea676c195d5f7d30a67c201fd32d
-config_hash: 5cb785fcdf07e4053f36b434e1db2d8a
+config_hash: 5b22718d6a289258de8dc7676abb51c5
From 22826f97b3edf8910eda4be67aa1c2e5dfdf200b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 16 Dec 2025 05:25:43 +0000
Subject: [PATCH 02/15] chore(internal): add missing files argument to base
client
---
src/hyperspell/_base_client.py | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/hyperspell/_base_client.py b/src/hyperspell/_base_client.py
index fbf8d394..a07a8778 100644
--- a/src/hyperspell/_base_client.py
+++ b/src/hyperspell/_base_client.py
@@ -1247,9 +1247,12 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+ opts = FinalRequestOptions.construct(
+ method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ )
return self.request(cast_to, opts)
def put(
@@ -1767,9 +1770,12 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+ opts = FinalRequestOptions.construct(
+ method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ )
return await self.request(cast_to, opts)
async def put(
From b39343e600358251c604ee2d0232b3af538172f5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 17 Dec 2025 08:25:53 +0000
Subject: [PATCH 03/15] chore: speedup initial import
---
src/hyperspell/_client.py | 314 ++++++++++++++++++++++++++++++--------
1 file changed, 253 insertions(+), 61 deletions(-)
diff --git a/src/hyperspell/_client.py b/src/hyperspell/_client.py
index a41a2c80..7d685187 100644
--- a/src/hyperspell/_client.py
+++ b/src/hyperspell/_client.py
@@ -3,7 +3,7 @@
from __future__ import annotations
import os
-from typing import Any, Mapping
+from typing import TYPE_CHECKING, Any, Mapping
from typing_extensions import Self, override
import httpx
@@ -20,8 +20,8 @@
not_given,
)
from ._utils import is_given, get_async_library
+from ._compat import cached_property
from ._version import __version__
-from .resources import auth, vaults, evaluate, memories, connections
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import APIStatusError, HyperspellError
from ._base_client import (
@@ -29,7 +29,15 @@
SyncAPIClient,
AsyncAPIClient,
)
-from .resources.integrations import integrations
+
+if TYPE_CHECKING:
+ from .resources import auth, vaults, evaluate, memories, connections, integrations
+ from .resources.auth import AuthResource, AsyncAuthResource
+ from .resources.vaults import VaultsResource, AsyncVaultsResource
+ from .resources.evaluate import EvaluateResource, AsyncEvaluateResource
+ from .resources.memories import MemoriesResource, AsyncMemoriesResource
+ from .resources.connections import ConnectionsResource, AsyncConnectionsResource
+ from .resources.integrations.integrations import IntegrationsResource, AsyncIntegrationsResource
__all__ = [
"Timeout",
@@ -44,15 +52,6 @@
class Hyperspell(SyncAPIClient):
- connections: connections.ConnectionsResource
- integrations: integrations.IntegrationsResource
- memories: memories.MemoriesResource
- evaluate: evaluate.EvaluateResource
- vaults: vaults.VaultsResource
- auth: auth.AuthResource
- with_raw_response: HyperspellWithRawResponse
- with_streaming_response: HyperspellWithStreamedResponse
-
# client options
api_key: str
user_id: str | None
@@ -111,14 +110,49 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
- self.connections = connections.ConnectionsResource(self)
- self.integrations = integrations.IntegrationsResource(self)
- self.memories = memories.MemoriesResource(self)
- self.evaluate = evaluate.EvaluateResource(self)
- self.vaults = vaults.VaultsResource(self)
- self.auth = auth.AuthResource(self)
- self.with_raw_response = HyperspellWithRawResponse(self)
- self.with_streaming_response = HyperspellWithStreamedResponse(self)
+ @cached_property
+ def connections(self) -> ConnectionsResource:
+ from .resources.connections import ConnectionsResource
+
+ return ConnectionsResource(self)
+
+ @cached_property
+ def integrations(self) -> IntegrationsResource:
+ from .resources.integrations import IntegrationsResource
+
+ return IntegrationsResource(self)
+
+ @cached_property
+ def memories(self) -> MemoriesResource:
+ from .resources.memories import MemoriesResource
+
+ return MemoriesResource(self)
+
+ @cached_property
+ def evaluate(self) -> EvaluateResource:
+ from .resources.evaluate import EvaluateResource
+
+ return EvaluateResource(self)
+
+ @cached_property
+ def vaults(self) -> VaultsResource:
+ from .resources.vaults import VaultsResource
+
+ return VaultsResource(self)
+
+ @cached_property
+ def auth(self) -> AuthResource:
+ from .resources.auth import AuthResource
+
+ return AuthResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> HyperspellWithRawResponse:
+ return HyperspellWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> HyperspellWithStreamedResponse:
+ return HyperspellWithStreamedResponse(self)
@property
@override
@@ -239,15 +273,6 @@ def _make_status_error(
class AsyncHyperspell(AsyncAPIClient):
- connections: connections.AsyncConnectionsResource
- integrations: integrations.AsyncIntegrationsResource
- memories: memories.AsyncMemoriesResource
- evaluate: evaluate.AsyncEvaluateResource
- vaults: vaults.AsyncVaultsResource
- auth: auth.AsyncAuthResource
- with_raw_response: AsyncHyperspellWithRawResponse
- with_streaming_response: AsyncHyperspellWithStreamedResponse
-
# client options
api_key: str
user_id: str | None
@@ -306,14 +331,49 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
- self.connections = connections.AsyncConnectionsResource(self)
- self.integrations = integrations.AsyncIntegrationsResource(self)
- self.memories = memories.AsyncMemoriesResource(self)
- self.evaluate = evaluate.AsyncEvaluateResource(self)
- self.vaults = vaults.AsyncVaultsResource(self)
- self.auth = auth.AsyncAuthResource(self)
- self.with_raw_response = AsyncHyperspellWithRawResponse(self)
- self.with_streaming_response = AsyncHyperspellWithStreamedResponse(self)
+ @cached_property
+ def connections(self) -> AsyncConnectionsResource:
+ from .resources.connections import AsyncConnectionsResource
+
+ return AsyncConnectionsResource(self)
+
+ @cached_property
+ def integrations(self) -> AsyncIntegrationsResource:
+ from .resources.integrations import AsyncIntegrationsResource
+
+ return AsyncIntegrationsResource(self)
+
+ @cached_property
+ def memories(self) -> AsyncMemoriesResource:
+ from .resources.memories import AsyncMemoriesResource
+
+ return AsyncMemoriesResource(self)
+
+ @cached_property
+ def evaluate(self) -> AsyncEvaluateResource:
+ from .resources.evaluate import AsyncEvaluateResource
+
+ return AsyncEvaluateResource(self)
+
+ @cached_property
+ def vaults(self) -> AsyncVaultsResource:
+ from .resources.vaults import AsyncVaultsResource
+
+ return AsyncVaultsResource(self)
+
+ @cached_property
+ def auth(self) -> AsyncAuthResource:
+ from .resources.auth import AsyncAuthResource
+
+ return AsyncAuthResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncHyperspellWithRawResponse:
+ return AsyncHyperspellWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncHyperspellWithStreamedResponse:
+ return AsyncHyperspellWithStreamedResponse(self)
@property
@override
@@ -434,43 +494,175 @@ def _make_status_error(
class HyperspellWithRawResponse:
+ _client: Hyperspell
+
def __init__(self, client: Hyperspell) -> None:
- self.connections = connections.ConnectionsResourceWithRawResponse(client.connections)
- self.integrations = integrations.IntegrationsResourceWithRawResponse(client.integrations)
- self.memories = memories.MemoriesResourceWithRawResponse(client.memories)
- self.evaluate = evaluate.EvaluateResourceWithRawResponse(client.evaluate)
- self.vaults = vaults.VaultsResourceWithRawResponse(client.vaults)
- self.auth = auth.AuthResourceWithRawResponse(client.auth)
+ self._client = client
+
+ @cached_property
+ def connections(self) -> connections.ConnectionsResourceWithRawResponse:
+ from .resources.connections import ConnectionsResourceWithRawResponse
+
+ return ConnectionsResourceWithRawResponse(self._client.connections)
+
+ @cached_property
+ def integrations(self) -> integrations.IntegrationsResourceWithRawResponse:
+ from .resources.integrations import IntegrationsResourceWithRawResponse
+
+ return IntegrationsResourceWithRawResponse(self._client.integrations)
+
+ @cached_property
+ def memories(self) -> memories.MemoriesResourceWithRawResponse:
+ from .resources.memories import MemoriesResourceWithRawResponse
+
+ return MemoriesResourceWithRawResponse(self._client.memories)
+
+ @cached_property
+ def evaluate(self) -> evaluate.EvaluateResourceWithRawResponse:
+ from .resources.evaluate import EvaluateResourceWithRawResponse
+
+ return EvaluateResourceWithRawResponse(self._client.evaluate)
+
+ @cached_property
+ def vaults(self) -> vaults.VaultsResourceWithRawResponse:
+ from .resources.vaults import VaultsResourceWithRawResponse
+
+ return VaultsResourceWithRawResponse(self._client.vaults)
+
+ @cached_property
+ def auth(self) -> auth.AuthResourceWithRawResponse:
+ from .resources.auth import AuthResourceWithRawResponse
+
+ return AuthResourceWithRawResponse(self._client.auth)
class AsyncHyperspellWithRawResponse:
+ _client: AsyncHyperspell
+
def __init__(self, client: AsyncHyperspell) -> None:
- self.connections = connections.AsyncConnectionsResourceWithRawResponse(client.connections)
- self.integrations = integrations.AsyncIntegrationsResourceWithRawResponse(client.integrations)
- self.memories = memories.AsyncMemoriesResourceWithRawResponse(client.memories)
- self.evaluate = evaluate.AsyncEvaluateResourceWithRawResponse(client.evaluate)
- self.vaults = vaults.AsyncVaultsResourceWithRawResponse(client.vaults)
- self.auth = auth.AsyncAuthResourceWithRawResponse(client.auth)
+ self._client = client
+
+ @cached_property
+ def connections(self) -> connections.AsyncConnectionsResourceWithRawResponse:
+ from .resources.connections import AsyncConnectionsResourceWithRawResponse
+
+ return AsyncConnectionsResourceWithRawResponse(self._client.connections)
+
+ @cached_property
+ def integrations(self) -> integrations.AsyncIntegrationsResourceWithRawResponse:
+ from .resources.integrations import AsyncIntegrationsResourceWithRawResponse
+
+ return AsyncIntegrationsResourceWithRawResponse(self._client.integrations)
+
+ @cached_property
+ def memories(self) -> memories.AsyncMemoriesResourceWithRawResponse:
+ from .resources.memories import AsyncMemoriesResourceWithRawResponse
+
+ return AsyncMemoriesResourceWithRawResponse(self._client.memories)
+
+ @cached_property
+ def evaluate(self) -> evaluate.AsyncEvaluateResourceWithRawResponse:
+ from .resources.evaluate import AsyncEvaluateResourceWithRawResponse
+
+ return AsyncEvaluateResourceWithRawResponse(self._client.evaluate)
+
+ @cached_property
+ def vaults(self) -> vaults.AsyncVaultsResourceWithRawResponse:
+ from .resources.vaults import AsyncVaultsResourceWithRawResponse
+
+ return AsyncVaultsResourceWithRawResponse(self._client.vaults)
+
+ @cached_property
+ def auth(self) -> auth.AsyncAuthResourceWithRawResponse:
+ from .resources.auth import AsyncAuthResourceWithRawResponse
+
+ return AsyncAuthResourceWithRawResponse(self._client.auth)
class HyperspellWithStreamedResponse:
+ _client: Hyperspell
+
def __init__(self, client: Hyperspell) -> None:
- self.connections = connections.ConnectionsResourceWithStreamingResponse(client.connections)
- self.integrations = integrations.IntegrationsResourceWithStreamingResponse(client.integrations)
- self.memories = memories.MemoriesResourceWithStreamingResponse(client.memories)
- self.evaluate = evaluate.EvaluateResourceWithStreamingResponse(client.evaluate)
- self.vaults = vaults.VaultsResourceWithStreamingResponse(client.vaults)
- self.auth = auth.AuthResourceWithStreamingResponse(client.auth)
+ self._client = client
+
+ @cached_property
+ def connections(self) -> connections.ConnectionsResourceWithStreamingResponse:
+ from .resources.connections import ConnectionsResourceWithStreamingResponse
+
+ return ConnectionsResourceWithStreamingResponse(self._client.connections)
+
+ @cached_property
+ def integrations(self) -> integrations.IntegrationsResourceWithStreamingResponse:
+ from .resources.integrations import IntegrationsResourceWithStreamingResponse
+
+ return IntegrationsResourceWithStreamingResponse(self._client.integrations)
+
+ @cached_property
+ def memories(self) -> memories.MemoriesResourceWithStreamingResponse:
+ from .resources.memories import MemoriesResourceWithStreamingResponse
+
+ return MemoriesResourceWithStreamingResponse(self._client.memories)
+
+ @cached_property
+ def evaluate(self) -> evaluate.EvaluateResourceWithStreamingResponse:
+ from .resources.evaluate import EvaluateResourceWithStreamingResponse
+
+ return EvaluateResourceWithStreamingResponse(self._client.evaluate)
+
+ @cached_property
+ def vaults(self) -> vaults.VaultsResourceWithStreamingResponse:
+ from .resources.vaults import VaultsResourceWithStreamingResponse
+
+ return VaultsResourceWithStreamingResponse(self._client.vaults)
+
+ @cached_property
+ def auth(self) -> auth.AuthResourceWithStreamingResponse:
+ from .resources.auth import AuthResourceWithStreamingResponse
+
+ return AuthResourceWithStreamingResponse(self._client.auth)
class AsyncHyperspellWithStreamedResponse:
+ _client: AsyncHyperspell
+
def __init__(self, client: AsyncHyperspell) -> None:
- self.connections = connections.AsyncConnectionsResourceWithStreamingResponse(client.connections)
- self.integrations = integrations.AsyncIntegrationsResourceWithStreamingResponse(client.integrations)
- self.memories = memories.AsyncMemoriesResourceWithStreamingResponse(client.memories)
- self.evaluate = evaluate.AsyncEvaluateResourceWithStreamingResponse(client.evaluate)
- self.vaults = vaults.AsyncVaultsResourceWithStreamingResponse(client.vaults)
- self.auth = auth.AsyncAuthResourceWithStreamingResponse(client.auth)
+ self._client = client
+
+ @cached_property
+ def connections(self) -> connections.AsyncConnectionsResourceWithStreamingResponse:
+ from .resources.connections import AsyncConnectionsResourceWithStreamingResponse
+
+ return AsyncConnectionsResourceWithStreamingResponse(self._client.connections)
+
+ @cached_property
+ def integrations(self) -> integrations.AsyncIntegrationsResourceWithStreamingResponse:
+ from .resources.integrations import AsyncIntegrationsResourceWithStreamingResponse
+
+ return AsyncIntegrationsResourceWithStreamingResponse(self._client.integrations)
+
+ @cached_property
+ def memories(self) -> memories.AsyncMemoriesResourceWithStreamingResponse:
+ from .resources.memories import AsyncMemoriesResourceWithStreamingResponse
+
+ return AsyncMemoriesResourceWithStreamingResponse(self._client.memories)
+
+ @cached_property
+ def evaluate(self) -> evaluate.AsyncEvaluateResourceWithStreamingResponse:
+ from .resources.evaluate import AsyncEvaluateResourceWithStreamingResponse
+
+ return AsyncEvaluateResourceWithStreamingResponse(self._client.evaluate)
+
+ @cached_property
+ def vaults(self) -> vaults.AsyncVaultsResourceWithStreamingResponse:
+ from .resources.vaults import AsyncVaultsResourceWithStreamingResponse
+
+ return AsyncVaultsResourceWithStreamingResponse(self._client.vaults)
+
+ @cached_property
+ def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse:
+ from .resources.auth import AsyncAuthResourceWithStreamingResponse
+
+ return AsyncAuthResourceWithStreamingResponse(self._client.auth)
Client = Hyperspell
From 4116107e99dbe09a060f53ebfedcf1ff58715674 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 18 Dec 2025 09:59:02 +0000
Subject: [PATCH 04/15] fix: use async_to_httpx_files in patch method
---
src/hyperspell/_base_client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/hyperspell/_base_client.py b/src/hyperspell/_base_client.py
index a07a8778..823c3a93 100644
--- a/src/hyperspell/_base_client.py
+++ b/src/hyperspell/_base_client.py
@@ -1774,7 +1774,7 @@ async def patch(
options: RequestOptions = {},
) -> ResponseT:
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)
From 9d02a464264ed187e3072c7b9337c14101b21945 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 19 Dec 2025 08:24:31 +0000
Subject: [PATCH 05/15] chore(internal): add `--fix` argument to lint script
---
scripts/lint | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/scripts/lint b/scripts/lint
index 6453a7bd..9d4a19f1 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -4,8 +4,13 @@ set -e
cd "$(dirname "$0")/.."
-echo "==> Running lints"
-rye run lint
+if [ "$1" = "--fix" ]; then
+ echo "==> Running lints with --fix"
+ rye run fix:ruff
+else
+ echo "==> Running lints"
+ rye run lint
+fi
echo "==> Making sure it imports"
rye run python -c 'import hyperspell'
From ddb8f8f25eb0f015edcb2cbb833316555a68adbf Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 6 Jan 2026 07:18:42 +0000
Subject: [PATCH 06/15] chore(internal): codegen related update
---
LICENSE | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/LICENSE b/LICENSE
index 9239282a..2634ff3d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2025 hyperspell
+Copyright 2026 hyperspell
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
From 115424d9ff7344bad70fd0a92657ae6f5fbfc647 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 6 Jan 2026 07:28:41 +0000
Subject: [PATCH 07/15] docs: prominently feature MCP server setup in root SDK
readmes
---
README.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/README.md b/README.md
index 65ced851..bdecbcb4 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,15 @@ and offers both synchronous and asynchronous clients powered by [httpx](https://
It is generated with [Stainless](https://www.stainless.com/).
+## MCP Server
+
+Use the Hyperspell MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.
+
+[](https://cursor.com/en-US/install-mcp?name=hyperspell-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImh5cGVyc3BlbGwtbWNwIl19)
+[](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22hyperspell-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22hyperspell-mcp%22%5D%7D)
+
+> Note: You may need to set environment variables in your MCP client.
+
## Documentation
The REST API documentation can be found on [docs.hyperspell.com](https://docs.hyperspell.com/). The full API of this library can be found in [api.md](api.md).
From 855385906fced24870ed2e7b24cffe0ad5856bbf Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 9 Jan 2026 19:29:41 +0000
Subject: [PATCH 08/15] feat(api): api update
---
.stats.yml | 6 +++---
README.md | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 72cccfe2..6a9449d9 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-fc4ab722e6762cc69d533f57bea0d70b00e44a30c4ad8144e14ff70a1170ec7c.yml
-openapi_spec_hash: 2533ea676c195d5f7d30a67c201fd32d
-config_hash: 5b22718d6a289258de8dc7676abb51c5
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a68e027e11cd18c52e0338be22775bf3ede2ec5f20924dec1cb15b9481ca9a49.yml
+openapi_spec_hash: 68a5cebb5b6e1c9d01f24971a7722d0c
+config_hash: b32e7b67898ff493ca749cdd513ab870
diff --git a/README.md b/README.md
index bdecbcb4..6080c5e0 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/).
Use the Hyperspell MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.
-[](https://cursor.com/en-US/install-mcp?name=hyperspell-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImh5cGVyc3BlbGwtbWNwIl19)
-[](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22hyperspell-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22hyperspell-mcp%22%5D%7D)
+[](https://cursor.com/en-US/install-mcp?name=hyperspell-mcp&config=eyJuYW1lIjoiaHlwZXJzcGVsbC1tY3AiLCJ0cmFuc3BvcnQiOiJzc2UiLCJ1cmwiOiJodHRwczovL2h5cGVyc3BlbGwuc3RsbWNwLmNvbS9zc2UifQ)
+[](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22hyperspell-mcp%22%2C%22type%22%3A%22sse%22%2C%22url%22%3A%22https%3A%2F%2Fhyperspell.stlmcp.com%2Fsse%22%7D)
> Note: You may need to set environment variables in your MCP client.
From b701ec51489e3100814dfd3295161cc04867e679 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 12 Jan 2026 05:29:45 +0000
Subject: [PATCH 09/15] feat(api): api update
---
.stats.yml | 4 +-
src/hyperspell/resources/memories.py | 440 ++----------------
src/hyperspell/types/auth_me_response.py | 88 +---
.../types/connection_list_response.py | 44 +-
.../types/integration_list_response.py | 46 +-
.../web_crawler_index_response.py | 44 +-
src/hyperspell/types/memory.py | 44 +-
.../types/memory_delete_response.py | 44 +-
src/hyperspell/types/memory_list_params.py | 44 +-
src/hyperspell/types/memory_search_params.py | 44 +-
src/hyperspell/types/memory_status.py | 44 +-
src/hyperspell/types/memory_update_params.py | 44 +-
12 files changed, 87 insertions(+), 843 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 6a9449d9..4f966b02 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a68e027e11cd18c52e0338be22775bf3ede2ec5f20924dec1cb15b9481ca9a49.yml
-openapi_spec_hash: 68a5cebb5b6e1c9d01f24971a7722d0c
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-010acb8c5c2c98ef5f9f11189dd51f3ccb952e4e55807152ea515b240f365860.yml
+openapi_spec_hash: c7f24079eeb76b92a22e2f621751a1fd
config_hash: b32e7b67898ff493ca749cdd513ab870
diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py
index 5d558e23..a8e3b1e8 100644
--- a/src/hyperspell/resources/memories.py
+++ b/src/hyperspell/resources/memories.py
@@ -62,51 +62,15 @@ def update(
*,
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
],
collection: Union[str, object, None] | Omit = omit,
metadata: Union[Dict[str, Union[str, float, bool]], object, None] | Omit = omit,
@@ -177,51 +141,15 @@ def list(
source: Optional[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
| Omit = omit,
@@ -282,51 +210,15 @@ def delete(
*,
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
],
# 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.
@@ -446,51 +338,15 @@ def get(
*,
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
],
# 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.
@@ -533,51 +389,15 @@ def search(
sources: List[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
| Omit = omit,
@@ -735,51 +555,15 @@ async def update(
*,
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
],
collection: Union[str, object, None] | Omit = omit,
metadata: Union[Dict[str, Union[str, float, bool]], object, None] | Omit = omit,
@@ -850,51 +634,15 @@ def list(
source: Optional[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
| Omit = omit,
@@ -955,51 +703,15 @@ async def delete(
*,
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
],
# 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.
@@ -1119,51 +831,15 @@ async def get(
*,
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
],
# 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.
@@ -1206,51 +882,15 @@ async def search(
sources: List[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
| Omit = omit,
diff --git a/src/hyperspell/types/auth_me_response.py b/src/hyperspell/types/auth_me_response.py
index 7eee2ac6..7cbf9af8 100644
--- a/src/hyperspell/types/auth_me_response.py
+++ b/src/hyperspell/types/auth_me_response.py
@@ -35,51 +35,15 @@ class AuthMeResponse(BaseModel):
available_integrations: List[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
"""All integrations available for the app"""
@@ -87,51 +51,15 @@ class AuthMeResponse(BaseModel):
installed_integrations: List[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
"""All integrations installed for the user"""
diff --git a/src/hyperspell/types/connection_list_response.py b/src/hyperspell/types/connection_list_response.py
index 0a60e0e6..8dec1b08 100644
--- a/src/hyperspell/types/connection_list_response.py
+++ b/src/hyperspell/types/connection_list_response.py
@@ -20,51 +20,15 @@ class Connection(BaseModel):
provider: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
"""The connection's provider"""
diff --git a/src/hyperspell/types/integration_list_response.py b/src/hyperspell/types/integration_list_response.py
index c189654f..e48fd87b 100644
--- a/src/hyperspell/types/integration_list_response.py
+++ b/src/hyperspell/types/integration_list_response.py
@@ -15,7 +15,7 @@ class Integration(BaseModel):
allow_multiple_connections: bool
"""Whether the integration allows multiple connections"""
- auth_provider: Literal["nango", "hyperspell", "composio", "whitelabel", "unified"]
+ auth_provider: Literal["nango", "unified", "whitelabel"]
"""The integration's auth provider"""
icon: str
@@ -26,51 +26,15 @@ class Integration(BaseModel):
provider: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
"""The integration's provider"""
diff --git a/src/hyperspell/types/integrations/web_crawler_index_response.py b/src/hyperspell/types/integrations/web_crawler_index_response.py
index deddf6e7..58a33f01 100644
--- a/src/hyperspell/types/integrations/web_crawler_index_response.py
+++ b/src/hyperspell/types/integrations/web_crawler_index_response.py
@@ -12,51 +12,15 @@ class WebCrawlerIndexResponse(BaseModel):
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
status: Literal["pending", "processing", "completed", "failed"]
diff --git a/src/hyperspell/types/memory.py b/src/hyperspell/types/memory.py
index df0de529..a03bab39 100644
--- a/src/hyperspell/types/memory.py
+++ b/src/hyperspell/types/memory.py
@@ -50,51 +50,15 @@ class Memory(BaseModel):
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
metadata: Optional[Metadata] = None
diff --git a/src/hyperspell/types/memory_delete_response.py b/src/hyperspell/types/memory_delete_response.py
index 7f5fbd62..19319f8b 100644
--- a/src/hyperspell/types/memory_delete_response.py
+++ b/src/hyperspell/types/memory_delete_response.py
@@ -16,51 +16,15 @@ class MemoryDeleteResponse(BaseModel):
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
success: bool
diff --git a/src/hyperspell/types/memory_list_params.py b/src/hyperspell/types/memory_list_params.py
index 5e01df30..55a5e339 100644
--- a/src/hyperspell/types/memory_list_params.py
+++ b/src/hyperspell/types/memory_list_params.py
@@ -25,51 +25,15 @@ class MemoryListParams(TypedDict, total=False):
source: Optional[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
"""Filter documents by source."""
diff --git a/src/hyperspell/types/memory_search_params.py b/src/hyperspell/types/memory_search_params.py
index a12d2359..d3b51e49 100644
--- a/src/hyperspell/types/memory_search_params.py
+++ b/src/hyperspell/types/memory_search_params.py
@@ -40,51 +40,15 @@ class MemorySearchParams(TypedDict, total=False):
sources: List[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
"""Only query documents from these sources."""
diff --git a/src/hyperspell/types/memory_status.py b/src/hyperspell/types/memory_status.py
index 3473d663..586933fb 100644
--- a/src/hyperspell/types/memory_status.py
+++ b/src/hyperspell/types/memory_status.py
@@ -12,51 +12,15 @@ class MemoryStatus(BaseModel):
source: Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
status: Literal["pending", "processing", "completed", "failed"]
diff --git a/src/hyperspell/types/memory_update_params.py b/src/hyperspell/types/memory_update_params.py
index 60ef2743..ece1a7a5 100644
--- a/src/hyperspell/types/memory_update_params.py
+++ b/src/hyperspell/types/memory_update_params.py
@@ -12,51 +12,15 @@ class MemoryUpdateParams(TypedDict, total=False):
source: Required[
Literal[
"collections",
- "vault",
- "web_crawler",
+ "reddit",
"notion",
"slack",
"google_calendar",
- "reddit",
+ "google_mail",
"box",
"google_drive",
- "airtable",
- "algolia",
- "amplitude",
- "asana",
- "ashby",
- "bamboohr",
- "basecamp",
- "bubbles",
- "calendly",
- "confluence",
- "clickup",
- "datadog",
- "deel",
- "discord",
- "dropbox",
- "exa",
- "facebook",
- "front",
- "github",
- "gitlab",
- "google_docs",
- "google_mail",
- "google_sheet",
- "hubspot",
- "jira",
- "linear",
- "microsoft_teams",
- "mixpanel",
- "monday",
- "outlook",
- "perplexity",
- "rippling",
- "salesforce",
- "segment",
- "todoist",
- "twitter",
- "zoom",
+ "vault",
+ "web_crawler",
]
]
From f77b3172f1eb0ff3e6e856ac168291fd067d8136 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 13 Jan 2026 16:30:04 +0000
Subject: [PATCH 10/15] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 4f966b02..f29edd2f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-010acb8c5c2c98ef5f9f11189dd51f3ccb952e4e55807152ea515b240f365860.yml
-openapi_spec_hash: c7f24079eeb76b92a22e2f621751a1fd
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-72723fb4502a7ccb382d8f2c71738540fcbc9a8eff8bf1467238d256d9990d3a.yml
+openapi_spec_hash: fdd38efc91067dc47dcb1e1676f70c5d
config_hash: b32e7b67898ff493ca749cdd513ab870
From 578c77664d05aa2ffa1c7d6cc3f7e3e3d5c77991 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 14 Jan 2026 10:53:48 +0000
Subject: [PATCH 11/15] feat(client): add support for binary request streaming
---
src/hyperspell/_base_client.py | 145 +++++++++++++++++++++++--
src/hyperspell/_models.py | 17 ++-
src/hyperspell/_types.py | 9 ++
tests/test_client.py | 189 ++++++++++++++++++++++++++++++++-
4 files changed, 346 insertions(+), 14 deletions(-)
diff --git a/src/hyperspell/_base_client.py b/src/hyperspell/_base_client.py
index 823c3a93..15df0a77 100644
--- a/src/hyperspell/_base_client.py
+++ b/src/hyperspell/_base_client.py
@@ -9,6 +9,7 @@
import inspect
import logging
import platform
+import warnings
import email.utils
from types import TracebackType
from random import random
@@ -51,9 +52,11 @@
ResponseT,
AnyMapping,
PostParser,
+ BinaryTypes,
RequestFiles,
HttpxSendArgs,
RequestOptions,
+ AsyncBinaryTypes,
HttpxRequestFiles,
ModelBuilderProtocol,
not_given,
@@ -477,8 +480,19 @@ def _build_request(
retries_taken: int = 0,
) -> httpx.Request:
if log.isEnabledFor(logging.DEBUG):
- log.debug("Request options: %s", model_dump(options, exclude_unset=True))
-
+ log.debug(
+ "Request options: %s",
+ model_dump(
+ options,
+ exclude_unset=True,
+ # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
+ exclude={
+ "content",
+ }
+ if PYDANTIC_V1
+ else {},
+ ),
+ )
kwargs: dict[str, Any] = {}
json_data = options.json_data
@@ -532,7 +546,13 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"
if is_body_allowed:
- if isinstance(json_data, bytes):
+ if options.content is not None and json_data is not None:
+ raise TypeError("Passing both `content` and `json_data` is not supported")
+ if options.content is not None and files is not None:
+ raise TypeError("Passing both `content` and `files` is not supported")
+ if options.content is not None:
+ kwargs["content"] = options.content
+ elif isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
@@ -1194,6 +1214,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[False] = False,
@@ -1206,6 +1227,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[True],
@@ -1219,6 +1241,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool,
@@ -1231,13 +1254,25 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
@@ -1247,11 +1282,23 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1261,11 +1308,23 @@ def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1275,9 +1334,19 @@ def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return self.request(cast_to, opts)
def get_api_list(
@@ -1717,6 +1786,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[False] = False,
@@ -1729,6 +1799,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[True],
@@ -1742,6 +1813,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool,
@@ -1754,13 +1826,25 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool = False,
stream_cls: type[_AsyncStreamT] | None = None,
) -> ResponseT | _AsyncStreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
@@ -1770,11 +1854,28 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="patch",
+ url=path,
+ json_data=body,
+ content=content,
+ files=await async_to_httpx_files(files),
+ **options,
)
return await self.request(cast_to, opts)
@@ -1784,11 +1885,23 @@ async def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)
@@ -1798,9 +1911,19 @@ async def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return await self.request(cast_to, opts)
def get_api_list(
diff --git a/src/hyperspell/_models.py b/src/hyperspell/_models.py
index ca9500b2..29070e05 100644
--- a/src/hyperspell/_models.py
+++ b/src/hyperspell/_models.py
@@ -3,7 +3,20 @@
import os
import inspect
import weakref
-from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Type,
+ Union,
+ Generic,
+ TypeVar,
+ Callable,
+ Iterable,
+ Optional,
+ AsyncIterable,
+ cast,
+)
from datetime import date, datetime
from typing_extensions import (
List,
@@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
timeout: float | Timeout | None
files: HttpxRequestFiles | None
idempotency_key: str
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
json_data: Body
extra_json: AnyMapping
follow_redirects: bool
@@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel):
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
follow_redirects: Union[bool, None] = None
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
# It should be noted that we cannot use `json` here as that would override
# a BaseModel method in an incompatible fashion.
json_data: Union[Body, None] = None
diff --git a/src/hyperspell/_types.py b/src/hyperspell/_types.py
index 59d3b796..c331e84c 100644
--- a/src/hyperspell/_types.py
+++ b/src/hyperspell/_types.py
@@ -13,9 +13,11 @@
Mapping,
TypeVar,
Callable,
+ Iterable,
Iterator,
Optional,
Sequence,
+ AsyncIterable,
)
from typing_extensions import (
Set,
@@ -56,6 +58,13 @@
else:
Base64FileInput = Union[IO[bytes], PathLike]
FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8.
+
+
+# Used for sending raw binary data / streaming data in request bodies
+# e.g. for file uploads without multipart encoding
+BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
+AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]
+
FileTypes = Union[
# file (or bytes)
FileContent,
diff --git a/tests/test_client.py b/tests/test_client.py
index 770b4bba..f3aef279 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -8,10 +8,11 @@
import json
import asyncio
import inspect
+import dataclasses
import tracemalloc
-from typing import Any, Union, cast
+from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast
from unittest import mock
-from typing_extensions import Literal
+from typing_extensions import Literal, AsyncIterator, override
import httpx
import pytest
@@ -36,6 +37,7 @@
from .utils import update_env
+T = TypeVar("T")
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = "My API Key"
user_id = "My User ID"
@@ -51,6 +53,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
return 0.1
+def mirror_request_content(request: httpx.Request) -> httpx.Response:
+ return httpx.Response(200, content=request.content)
+
+
+# note: we can't use the httpx.MockTransport class as it consumes the request
+# body itself, which means we can't test that the body is read lazily
+class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport):
+ def __init__(
+ self,
+ handler: Callable[[httpx.Request], httpx.Response]
+ | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]],
+ ) -> None:
+ self.handler = handler
+
+ @override
+ def handle_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function"
+ assert inspect.isfunction(self.handler), "handler must be a function"
+ return self.handler(request)
+
+ @override
+ async def handle_async_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function"
+ return await self.handler(request)
+
+
+@dataclasses.dataclass
+class Counter:
+ value: int = 0
+
+
+def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
+async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
def _get_open_connections(client: Hyperspell | AsyncHyperspell) -> int:
transport = client._client._transport
assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
@@ -543,6 +596,71 @@ def test_multipart_repeating_array(self, client: Hyperspell) -> None:
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload(self, respx_mock: MockRouter, client: Hyperspell) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ def test_binary_content_upload_with_iterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_sync_iterator([file_content], counter=counter)
+
+ def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=request.read())
+
+ with Hyperspell(
+ base_url=base_url,
+ api_key=api_key,
+ user_id=user_id,
+ _strict_response_validation=True,
+ http_client=httpx.Client(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Hyperspell) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
def test_basic_union_response(self, respx_mock: MockRouter, client: Hyperspell) -> None:
class Model1(BaseModel):
@@ -1434,6 +1552,73 @@ def test_multipart_repeating_array(self, async_client: AsyncHyperspell) -> None:
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncHyperspell) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = await async_client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ async def test_binary_content_upload_with_asynciterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_async_iterator([file_content], counter=counter)
+
+ async def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=await request.aread())
+
+ async with AsyncHyperspell(
+ base_url=base_url,
+ api_key=api_key,
+ user_id=user_id,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = await client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload_with_body_is_deprecated(
+ self, respx_mock: MockRouter, async_client: AsyncHyperspell
+ ) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = await async_client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncHyperspell) -> None:
class Model1(BaseModel):
From c4843e39e321536cb57a1cbc88da8d221d7c46e9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 16 Jan 2026 22:45:35 +0000
Subject: [PATCH 12/15] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index f29edd2f..047304ed 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-72723fb4502a7ccb382d8f2c71738540fcbc9a8eff8bf1467238d256d9990d3a.yml
-openapi_spec_hash: fdd38efc91067dc47dcb1e1676f70c5d
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a73c73c4848db6cc0b836219f2ace7bc9f6b4611d36f9daa2158f8bd5a7d0864.yml
+openapi_spec_hash: 4ef2aeca3ffe2c6e6fbca0770a69c6fb
config_hash: b32e7b67898ff493ca749cdd513ab870
From a4d305c10a9364f499b2cd9eaf0fc02bb7dcb136 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 16 Jan 2026 22:46:47 +0000
Subject: [PATCH 13/15] feat(api): manual updates
---
.stats.yml | 4 +-
api.md | 9 +-
src/hyperspell/resources/memories.py | 98 ++++++++++++++++++-
src/hyperspell/types/__init__.py | 2 +
.../types/memory_add_bulk_params.py | 50 ++++++++++
.../types/memory_add_bulk_response.py | 20 ++++
tests/api_resources/test_memories.py | 63 ++++++++++++
7 files changed, 242 insertions(+), 4 deletions(-)
create mode 100644 src/hyperspell/types/memory_add_bulk_params.py
create mode 100644 src/hyperspell/types/memory_add_bulk_response.py
diff --git a/.stats.yml b/.stats.yml
index 047304ed..b7a86017 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 22
+configured_endpoints: 23
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a73c73c4848db6cc0b836219f2ace7bc9f6b4611d36f9daa2158f8bd5a7d0864.yml
openapi_spec_hash: 4ef2aeca3ffe2c6e6fbca0770a69c6fb
-config_hash: b32e7b67898ff493ca749cdd513ab870
+config_hash: bd77d0b7029518c697756456d6854f07
diff --git a/api.md b/api.md
index 8cfcaeda..079f9be6 100644
--- a/api.md
+++ b/api.md
@@ -65,7 +65,13 @@ Methods:
Types:
```python
-from hyperspell.types import Memory, MemoryStatus, MemoryDeleteResponse, MemoryStatusResponse
+from hyperspell.types import (
+ Memory,
+ MemoryStatus,
+ MemoryDeleteResponse,
+ MemoryAddBulkResponse,
+ MemoryStatusResponse,
+)
```
Methods:
@@ -74,6 +80,7 @@ Methods:
- client.memories.list(\*\*params) -> SyncCursorPage[Memory]
- client.memories.delete(resource_id, \*, source) -> MemoryDeleteResponse
- client.memories.add(\*\*params) -> MemoryStatus
+- client.memories.add_bulk(\*\*params) -> MemoryAddBulkResponse
- client.memories.get(resource_id, \*, source) -> Memory
- client.memories.search(\*\*params) -> QueryResult
- client.memories.status() -> MemoryStatusResponse
diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py
index a8e3b1e8..b18948db 100644
--- a/src/hyperspell/resources/memories.py
+++ b/src/hyperspell/resources/memories.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict, List, Union, Mapping, Optional, cast
+from typing import Dict, List, Union, Mapping, Iterable, Optional, cast
from datetime import datetime
from typing_extensions import Literal
@@ -14,6 +14,7 @@
memory_search_params,
memory_update_params,
memory_upload_params,
+ memory_add_bulk_params,
)
from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
@@ -32,6 +33,7 @@
from ..types.shared.query_result import QueryResult
from ..types.memory_delete_response import MemoryDeleteResponse
from ..types.memory_status_response import MemoryStatusResponse
+from ..types.memory_add_bulk_response import MemoryAddBulkResponse
__all__ = ["MemoriesResource", "AsyncMemoriesResource"]
@@ -332,6 +334,47 @@ def add(
cast_to=MemoryStatus,
)
+ def add_bulk(
+ self,
+ *,
+ items: Iterable[memory_add_bulk_params.Item],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> MemoryAddBulkResponse:
+ """
+ Adds multiple documents to the index in a single request.
+
+ All items are validated before any database operations occur. If any item fails
+ validation, the entire batch is rejected with a 422 error detailing which items
+ failed and why.
+
+ Maximum 100 items per request. Each item follows the same schema as the
+ single-item /memories/add endpoint.
+
+ Args:
+ items: List of memories to ingest. Maximum 100 items.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/memories/add/bulk",
+ body=maybe_transform({"items": items}, memory_add_bulk_params.MemoryAddBulkParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=MemoryAddBulkResponse,
+ )
+
def get(
self,
resource_id: str,
@@ -825,6 +868,47 @@ async def add(
cast_to=MemoryStatus,
)
+ async def add_bulk(
+ self,
+ *,
+ items: Iterable[memory_add_bulk_params.Item],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> MemoryAddBulkResponse:
+ """
+ Adds multiple documents to the index in a single request.
+
+ All items are validated before any database operations occur. If any item fails
+ validation, the entire batch is rejected with a 422 error detailing which items
+ failed and why.
+
+ Maximum 100 items per request. Each item follows the same schema as the
+ single-item /memories/add endpoint.
+
+ Args:
+ items: List of memories to ingest. Maximum 100 items.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/memories/add/bulk",
+ body=await async_maybe_transform({"items": items}, memory_add_bulk_params.MemoryAddBulkParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=MemoryAddBulkResponse,
+ )
+
async def get(
self,
resource_id: str,
@@ -1038,6 +1122,9 @@ def __init__(self, memories: MemoriesResource) -> None:
self.add = to_raw_response_wrapper(
memories.add,
)
+ self.add_bulk = to_raw_response_wrapper(
+ memories.add_bulk,
+ )
self.get = to_raw_response_wrapper(
memories.get,
)
@@ -1068,6 +1155,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
self.add = async_to_raw_response_wrapper(
memories.add,
)
+ self.add_bulk = async_to_raw_response_wrapper(
+ memories.add_bulk,
+ )
self.get = async_to_raw_response_wrapper(
memories.get,
)
@@ -1098,6 +1188,9 @@ def __init__(self, memories: MemoriesResource) -> None:
self.add = to_streamed_response_wrapper(
memories.add,
)
+ self.add_bulk = to_streamed_response_wrapper(
+ memories.add_bulk,
+ )
self.get = to_streamed_response_wrapper(
memories.get,
)
@@ -1128,6 +1221,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
self.add = async_to_streamed_response_wrapper(
memories.add,
)
+ self.add_bulk = async_to_streamed_response_wrapper(
+ memories.add_bulk,
+ )
self.get = async_to_streamed_response_wrapper(
memories.get,
)
diff --git a/src/hyperspell/types/__init__.py b/src/hyperspell/types/__init__.py
index 434ea44e..64dcb9a9 100644
--- a/src/hyperspell/types/__init__.py
+++ b/src/hyperspell/types/__init__.py
@@ -15,9 +15,11 @@
from .memory_update_params import MemoryUpdateParams as MemoryUpdateParams
from .memory_upload_params import MemoryUploadParams as MemoryUploadParams
from .auth_user_token_params import AuthUserTokenParams as AuthUserTokenParams
+from .memory_add_bulk_params import MemoryAddBulkParams as MemoryAddBulkParams
from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse
from .memory_status_response import MemoryStatusResponse as MemoryStatusResponse
from .connection_list_response import ConnectionListResponse as ConnectionListResponse
+from .memory_add_bulk_response import MemoryAddBulkResponse as MemoryAddBulkResponse
from .auth_delete_user_response import AuthDeleteUserResponse as AuthDeleteUserResponse
from .integration_list_response import IntegrationListResponse as IntegrationListResponse
from .connection_revoke_response import ConnectionRevokeResponse as ConnectionRevokeResponse
diff --git a/src/hyperspell/types/memory_add_bulk_params.py b/src/hyperspell/types/memory_add_bulk_params.py
new file mode 100644
index 00000000..1422a541
--- /dev/null
+++ b/src/hyperspell/types/memory_add_bulk_params.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Iterable, Optional
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["MemoryAddBulkParams", "Item"]
+
+
+class MemoryAddBulkParams(TypedDict, total=False):
+ items: Required[Iterable[Item]]
+ """List of memories to ingest. Maximum 100 items."""
+
+
+class Item(TypedDict, total=False):
+ text: Required[str]
+ """Full text of the document."""
+
+ collection: Optional[str]
+ """The collection to add the document to for easier retrieval."""
+
+ date: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+ """Date of the document.
+
+ Depending on the document, this could be the creation date or date the document
+ was last updated (eg. for a chat transcript, this would be the date of the last
+ message). This helps the ranking algorithm and allows you to filter by date
+ range.
+ """
+
+ metadata: Optional[Dict[str, Union[str, float, bool]]]
+ """Custom metadata for filtering.
+
+ Keys must be alphanumeric with underscores, max 64 chars. Values must be string,
+ number, or boolean.
+ """
+
+ resource_id: str
+ """The resource ID to add the document to.
+
+ If not provided, a new resource ID will be generated. If provided, the document
+ will be updated if it already exists.
+ """
+
+ title: Optional[str]
+ """Title of the document."""
diff --git a/src/hyperspell/types/memory_add_bulk_response.py b/src/hyperspell/types/memory_add_bulk_response.py
new file mode 100644
index 00000000..0cb65978
--- /dev/null
+++ b/src/hyperspell/types/memory_add_bulk_response.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .._models import BaseModel
+from .memory_status import MemoryStatus
+
+__all__ = ["MemoryAddBulkResponse"]
+
+
+class MemoryAddBulkResponse(BaseModel):
+ """Response schema for successful bulk ingestion."""
+
+ count: int
+ """Number of items successfully processed"""
+
+ items: List[MemoryStatus]
+ """Status of each ingested item"""
+
+ success: Optional[bool] = None
diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py
index 36f9f677..4a13bb22 100644
--- a/tests/api_resources/test_memories.py
+++ b/tests/api_resources/test_memories.py
@@ -14,6 +14,7 @@
MemoryStatus,
MemoryDeleteResponse,
MemoryStatusResponse,
+ MemoryAddBulkResponse,
)
from hyperspell._utils import parse_datetime
from hyperspell.pagination import SyncCursorPage, AsyncCursorPage
@@ -200,6 +201,37 @@ def test_streaming_response_add(self, client: Hyperspell) -> None:
assert cast(Any, response.is_closed) is True
+ @parametrize
+ def test_method_add_bulk(self, client: Hyperspell) -> None:
+ memory = client.memories.add_bulk(
+ items=[{"text": "..."}],
+ )
+ assert_matches_type(MemoryAddBulkResponse, memory, path=["response"])
+
+ @parametrize
+ def test_raw_response_add_bulk(self, client: Hyperspell) -> None:
+ response = client.memories.with_raw_response.add_bulk(
+ items=[{"text": "..."}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ memory = response.parse()
+ assert_matches_type(MemoryAddBulkResponse, memory, path=["response"])
+
+ @parametrize
+ def test_streaming_response_add_bulk(self, client: Hyperspell) -> None:
+ with client.memories.with_streaming_response.add_bulk(
+ items=[{"text": "..."}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ memory = response.parse()
+ assert_matches_type(MemoryAddBulkResponse, memory, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@parametrize
def test_method_get(self, client: Hyperspell) -> None:
memory = client.memories.get(
@@ -603,6 +635,37 @@ async def test_streaming_response_add(self, async_client: AsyncHyperspell) -> No
assert cast(Any, response.is_closed) is True
+ @parametrize
+ async def test_method_add_bulk(self, async_client: AsyncHyperspell) -> None:
+ memory = await async_client.memories.add_bulk(
+ items=[{"text": "..."}],
+ )
+ assert_matches_type(MemoryAddBulkResponse, memory, path=["response"])
+
+ @parametrize
+ async def test_raw_response_add_bulk(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.memories.with_raw_response.add_bulk(
+ items=[{"text": "..."}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ memory = await response.parse()
+ assert_matches_type(MemoryAddBulkResponse, memory, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_add_bulk(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.memories.with_streaming_response.add_bulk(
+ items=[{"text": "..."}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ memory = await response.parse()
+ assert_matches_type(MemoryAddBulkResponse, memory, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@parametrize
async def test_method_get(self, async_client: AsyncHyperspell) -> None:
memory = await async_client.memories.get(
From 2453986d89b02dd99528eb40676ef0a087391e29 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 17 Jan 2026 07:45:39 +0000
Subject: [PATCH 14/15] chore(internal): update `actions/checkout` version
---
.github/workflows/ci.yml | 6 +++---
.github/workflows/publish-pypi.yml | 2 +-
.github/workflows/release-doctor.yml | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7e334a01..938ce889 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -44,7 +44,7 @@ jobs:
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -81,7 +81,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 50e544ee..53338790 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 9d43ec89..7f739e2a 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'hyperspell/python-sdk' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Check release environment
run: |
From ad0381c939283205bbac5fae5d116780c1b1833b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 17 Jan 2026 07:45:56 +0000
Subject: [PATCH 15/15] release: 0.30.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 30 ++++++++++++++++++++++++++++++
pyproject.toml | 2 +-
src/hyperspell/_version.py | 2 +-
4 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 8935e932..554e34bb 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.28.0"
+ ".": "0.30.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0dab305..ebe56bb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,35 @@
# Changelog
+## 0.30.0 (2026-01-17)
+
+Full Changelog: [v0.28.0...v0.30.0](https://github.com/hyperspell/python-sdk/compare/v0.28.0...v0.30.0)
+
+### Features
+
+* **api:** api update ([b701ec5](https://github.com/hyperspell/python-sdk/commit/b701ec51489e3100814dfd3295161cc04867e679))
+* **api:** api update ([8553859](https://github.com/hyperspell/python-sdk/commit/855385906fced24870ed2e7b24cffe0ad5856bbf))
+* **api:** manual updates ([a4d305c](https://github.com/hyperspell/python-sdk/commit/a4d305c10a9364f499b2cd9eaf0fc02bb7dcb136))
+* **client:** add support for binary request streaming ([578c776](https://github.com/hyperspell/python-sdk/commit/578c77664d05aa2ffa1c7d6cc3f7e3e3d5c77991))
+
+
+### Bug Fixes
+
+* use async_to_httpx_files in patch method ([4116107](https://github.com/hyperspell/python-sdk/commit/4116107e99dbe09a060f53ebfedcf1ff58715674))
+
+
+### Chores
+
+* **internal:** add `--fix` argument to lint script ([9d02a46](https://github.com/hyperspell/python-sdk/commit/9d02a464264ed187e3072c7b9337c14101b21945))
+* **internal:** add missing files argument to base client ([22826f9](https://github.com/hyperspell/python-sdk/commit/22826f97b3edf8910eda4be67aa1c2e5dfdf200b))
+* **internal:** codegen related update ([ddb8f8f](https://github.com/hyperspell/python-sdk/commit/ddb8f8f25eb0f015edcb2cbb833316555a68adbf))
+* **internal:** update `actions/checkout` version ([2453986](https://github.com/hyperspell/python-sdk/commit/2453986d89b02dd99528eb40676ef0a087391e29))
+* speedup initial import ([b39343e](https://github.com/hyperspell/python-sdk/commit/b39343e600358251c604ee2d0232b3af538172f5))
+
+
+### Documentation
+
+* prominently feature MCP server setup in root SDK readmes ([115424d](https://github.com/hyperspell/python-sdk/commit/115424d9ff7344bad70fd0a92657ae6f5fbfc647))
+
## 0.28.0 (2025-12-15)
Full Changelog: [v0.27.0...v0.28.0](https://github.com/hyperspell/python-sdk/compare/v0.27.0...v0.28.0)
diff --git a/pyproject.toml b/pyproject.toml
index 87fff548..8f3ea649 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "hyperspell"
-version = "0.28.0"
+version = "0.30.0"
description = "The official Python library for the hyperspell API"
dynamic = ["readme"]
license = "MIT"
diff --git a/src/hyperspell/_version.py b/src/hyperspell/_version.py
index 1223a2c6..aee8df02 100644
--- a/src/hyperspell/_version.py
+++ b/src/hyperspell/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "hyperspell"
-__version__ = "0.28.0" # x-release-please-version
+__version__ = "0.30.0" # x-release-please-version