Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to the ChatBotKit Python SDK are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2026-06-27

### Added

- Secret token minting and request proxying. `client.secret.mint(...)` /
`client.contact.secret.mint(...)` mint a usable token from a secret
(`oauth`/`jwt` secrets only; owner-only) and return `{ token, expiresAt }`.
`client.secret.proxy(...)` / `client.contact.secret.proxy(...)` proxy a request
through a secret — the credential is injected server-side (it never leaves the
platform) and the raw upstream `httpx.Response` is returned verbatim. A non-2xx
status (including `409 authorization_required`) is returned, not raised.

## [0.3.0] - 2026-06-26

### Added
Expand Down
2 changes: 1 addition & 1 deletion chatbotkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
"Response",
]

__version__ = "0.3.0"
__version__ = "0.4.0"
7 changes: 6 additions & 1 deletion chatbotkit/_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ async def request(
record: Any = None,
headers: Mapping[str, str] | None = None,
endpoint: str | None = None,
raw: bool = False,
) -> httpx.Response:
request = self._build_request(
path,
Expand All @@ -263,7 +264,11 @@ async def request(
)

response = await self._http.request(**request)
await self.raise_for_status(response)

# in raw (passthrough) mode the caller wants the response verbatim - do
# not raise on a non-2xx status (e.g. the proxy's 409 authorization_required)
if not raw:
await self.raise_for_status(response)

return response

Expand Down
32 changes: 32 additions & 0 deletions chatbotkit/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import Any, Mapping

import httpx

from . import types
from ._transport import Client, Response

Expand Down Expand Up @@ -115,6 +117,36 @@ def list(
stream_parse=types.ContactSecretListStreamItem.from_dict,
)

def mint(
self,
contact_id: str,
secret_id: str,
) -> Response[types.ContactSecretMintResponse, Any]:
"""Mint a usable token from a contact's secret (owner-only; oauth/jwt only)."""
return self._client.client_fetch(
f"/api/v1/contact/{contact_id}/secret/{secret_id}/mint",
record={},
parse=types.ContactSecretMintResponse.from_dict,
)

async def proxy(
self,
contact_id: str,
secret_id: str,
request: types.ContactSecretProxyRequest | Request,
) -> httpx.Response:
"""Proxy a request through a contact's secret, injected server-side.

Returns the raw upstream response verbatim; a non-2xx status is returned,
not raised.
"""
return await self._client.request(
f"/api/v1/contact/{contact_id}/secret/{secret_id}/proxy",
method="POST",
record=request,
raw=True,
)


class ContactSpaceClient:
def __init__(self, client: Client) -> None:
Expand Down
27 changes: 27 additions & 0 deletions chatbotkit/secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import Any, Mapping

import httpx

from . import types
from ._transport import Client, Response

Expand Down Expand Up @@ -60,3 +62,28 @@ def delete(
record=request or {},
parse=types.SecretDeleteResponse.from_dict,
)

def mint(self, secret_id: str) -> Response[types.SecretMintResponse, Any]:
"""Mint a usable token from the secret (owner-only; oauth/jwt only)."""
return self._client.client_fetch(
f"/api/v1/secret/{secret_id}/mint",
record={},
parse=types.SecretMintResponse.from_dict,
)

async def proxy(
self,
secret_id: str,
request: types.SecretProxyRequest | Request,
) -> httpx.Response:
"""Proxy a request through the secret, injected server-side.

Returns the raw upstream response verbatim; a non-2xx status (including
409 authorization_required) is returned, not raised.
"""
return await self._client.request(
f"/api/v1/secret/{secret_id}/proxy",
method="POST",
record=request,
raw=True,
)
Loading
Loading