diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 69d849f..4b521a4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: ${{ github.repository == 'stainless-sdks/keycard-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -41,7 +41,7 @@ jobs:
permissions:
contents: read
id-token: write
- runs-on: ${{ github.repository == 'stainless-sdks/keycard-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -78,7 +78,7 @@ jobs:
test:
timeout-minutes: 10
name: test
- runs-on: ${{ github.repository == 'stainless-sdks/keycard-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b4e9013..6db19b9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.16.0"
+ ".": "0.17.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index ffd5937..98e0761 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 106
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard/keycard-api-cdf14bee924cea2eaca3c1aaed26521cb91369d9462d22c21a0306b5786f4706.yml
-openapi_spec_hash: d8349acc1adec880977b6167a1e866bc
-config_hash: 7adc6b24545570dcc4a1bf0f714aa3e0
+configured_endpoints: 109
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard/keycard-api-d267ee9fc7304706089638c23aafc822b8867516bb6e85e02a9f5111be206ee1.yml
+openapi_spec_hash: dfad2e134540bd15e6e31f1e6a4e8fbc
+config_hash: 22d144a33ba2901c82cc1f387a83f9a7
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cefd0e4..3f8dbf1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,37 @@
# Changelog
+## 0.17.0 (2026-07-31)
+
+Full Changelog: [v0.16.0...v0.17.0](https://github.com/keycardai/keycard-python/compare/v0.16.0...v0.17.0)
+
+### Features
+
+* **ACC-709:** application assignees + expose role assignments ([6a76dbf](https://github.com/keycardai/keycard-python/commit/6a76dbf04cbd9d16c1054a262a7a42a01668f035))
+* **applications:** allow unified-gateway and mcp-server traits (ECO-128) ([414ac68](https://github.com/keycardai/keycard-python/commit/414ac68a77bb5c54837b7d4146c2e8b8735f6370))
+* filter users by identifier in management list ([147af02](https://github.com/keycardai/keycard-python/commit/147af020e8e8c31678928137fb85386b54dd4ec6))
+* **ID-365:** support additional SSO provider configuration options ([526b229](https://github.com/keycardai/keycard-python/commit/526b22958acc91a11569ba3f8453a004eb03f6f5))
+* **providers:** store brand icon_url in provider metadata ([f298c38](https://github.com/keycardai/keycard-python/commit/f298c382bdbe7e68b8a1556f36fe466b6155a5a8))
+* **resources:** add filter[owner_type] to the resource list (ECO-225) ([ede883a](https://github.com/keycardai/keycard-python/commit/ede883a754b07dfd8beff31afd57860d8d65814b))
+* **resources:** add search, sort, and filter[id] to the paginated resource list ([ca1681d](https://github.com/keycardai/keycard-python/commit/ca1681d46b70bd5e2913a4a8906793bc55c34ca7))
+* **resources:** filter the resource list by trait ([6cab4f0](https://github.com/keycardai/keycard-python/commit/6cab4f0985ac778837b272353cf1f729b88bd9d9))
+* **sdk:** generate a client for the existing /policy/bundle endpoint ([518c473](https://github.com/keycardai/keycard-python/commit/518c473dcb23f0d68e849838a070187addf422a9))
+* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([1c22f7e](https://github.com/keycardai/keycard-python/commit/1c22f7ea4ae904c4864a794dc4aaa46afa7d7e0e))
+
+
+### Bug Fixes
+
+* **ACC-613:** preserve source order of policies in draft/convert cedar_json ([6d27d21](https://github.com/keycardai/keycard-python/commit/6d27d213ac66fb9f1890409a380924c3edb44b99))
+* **ci:** resolve Stainless error diagnostics and enforce fail_on: error ([8a01934](https://github.com/keycardai/keycard-python/commit/8a01934b6ceb8636db998f0498d86ccb810cf639))
+* exact-match identifier filter on resources management list ([846f24e](https://github.com/keycardai/keycard-python/commit/846f24efdb2492e4ea0741167ebc7153af5d0d1e))
+* **internal:** resolve build failures ([15cb317](https://github.com/keycardai/keycard-python/commit/15cb317c1d1ea8462b30bff1411e52a554df0fb0))
+* **sdk:** generate /policy/bundle body as raw binary, not multipart ([d516a7f](https://github.com/keycardai/keycard-python/commit/d516a7f7b207aa4eed33d1a401721b9aa1ecd5d9))
+
+
+### Chores
+
+* de-dup and align types across API specs ([bf16103](https://github.com/keycardai/keycard-python/commit/bf161039858f079eabd6abf9102fa439633b9bc9))
+* Fixes found during Terraform work ([8389f30](https://github.com/keycardai/keycard-python/commit/8389f304ffae7c4bbe512de541a6896469be273f))
+
## 0.16.0 (2026-06-30)
Full Changelog: [v0.15.0...v0.16.0](https://github.com/keycardai/keycard-python/compare/v0.15.0...v0.16.0)
diff --git a/README.md b/README.md
index cf222f1..e364722 100644
--- a/README.md
+++ b/README.md
@@ -25,23 +25,34 @@ pip install keycardai_api
The full API of this library can be found in [api.md](api.md).
```python
+import os
from keycardai_api import KeycardAPI
-client = KeycardAPI()
+client = KeycardAPI(
+ api_key=os.environ.get("KEYCARD_API_API_KEY"), # This is the default and can be omitted
+)
zones = client.zones.list()
print(zones.items)
```
+While you can provide an `api_key` keyword argument,
+we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
+to add `KEYCARD_API_API_KEY="My API Key"` to your `.env` file
+so that your API Key is not stored in source control.
+
## Async usage
Simply import `AsyncKeycardAPI` instead of `KeycardAPI` and use `await` with each API call:
```python
+import os
import asyncio
from keycardai_api import AsyncKeycardAPI
-client = AsyncKeycardAPI()
+client = AsyncKeycardAPI(
+ api_key=os.environ.get("KEYCARD_API_API_KEY"), # This is the default and can be omitted
+)
async def main() -> None:
@@ -68,6 +79,7 @@ pip install keycardai_api[aiohttp]
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
```python
+import os
import asyncio
from keycardai_api import DefaultAioHttpClient
from keycardai_api import AsyncKeycardAPI
@@ -75,6 +87,7 @@ from keycardai_api import AsyncKeycardAPI
async def main() -> None:
async with AsyncKeycardAPI(
+ api_key=os.environ.get("KEYCARD_API_API_KEY"), # This is the default and can be omitted
http_client=DefaultAioHttpClient(),
) as client:
zones = await client.zones.list()
diff --git a/api.md b/api.md
index 48d50a6..81bbd7d 100644
--- a/api.md
+++ b/api.md
@@ -261,46 +261,6 @@ Methods:
- client.zones.policies.versions.list(policy_id, \*, zone_id, \*\*params) -> VersionListResponse
- client.zones.policies.versions.archive(version_id, \*, zone_id, policy_id) -> PolicyVersion
-## Packages
-
-Types:
-
-```python
-from keycardai_api.types.zones import (
- InputState,
- Package,
- PackageDraft,
- PackageInputBinding,
- PackageList,
- PackageOutputBinding,
- PackageSource,
-)
-```
-
-### Versions
-
-Types:
-
-```python
-from keycardai_api.types.zones.packages import PackageVersion, PackageVersionList
-```
-
-## Installs
-
-Types:
-
-```python
-from keycardai_api.types.zones import Install, InstallList, InstallStatus
-```
-
-## CatalogTasks
-
-Types:
-
-```python
-from keycardai_api.types.zones import Task, TaskOperation, TaskStatus
-```
-
## PolicySets
Types:
@@ -470,3 +430,11 @@ Methods:
- client.invitations.retrieve(token) -> InvitationRetrieveResponse
- client.invitations.accept(token) -> InvitationAcceptResponse
+
+# PolicyBundle
+
+Methods:
+
+- client.policy_bundle.retrieve() -> BinaryAPIResponse
+- client.policy_bundle.update(body, \*\*params) -> BinaryAPIResponse
+- client.policy_bundle.reset() -> None
diff --git a/pyproject.toml b/pyproject.toml
index 0f72996..a2de0a3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "keycardai_api"
-version = "0.16.0"
+version = "0.17.0"
description = "The official Python library for the keycard-api API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/scripts/lint b/scripts/lint
index 97303ed..7b9389c 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -13,7 +13,7 @@ else
fi
echo "==> Running pyright"
-uv run pyright
+uv run pyright -p .
echo "==> Running mypy"
uv run mypy .
diff --git a/src/keycardai_api/_client.py b/src/keycardai_api/_client.py
index 103db47..d76b6ee 100644
--- a/src/keycardai_api/_client.py
+++ b/src/keycardai_api/_client.py
@@ -37,9 +37,10 @@
)
if TYPE_CHECKING:
- from .resources import zones, invitations, organizations
+ from .resources import zones, invitations, organizations, policy_bundle
from .resources.invitations import InvitationsResource, AsyncInvitationsResource
from .resources.zones.zones import ZonesResource, AsyncZonesResource
+ from .resources.policy_bundle import PolicyBundleResource, AsyncPolicyBundleResource
from .resources.organizations.organizations import OrganizationsResource, AsyncOrganizationsResource
__all__ = [
@@ -147,6 +148,35 @@ def invitations(self) -> InvitationsResource:
return InvitationsResource(self)
+ @cached_property
+ def policy_bundle(self) -> PolicyBundleResource:
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+ from .resources.policy_bundle import PolicyBundleResource
+
+ return PolicyBundleResource(self)
+
@cached_property
def with_raw_response(self) -> KeycardAPIWithRawResponse:
return KeycardAPIWithRawResponse(self)
@@ -160,12 +190,27 @@ def with_streaming_response(self) -> KeycardAPIWithStreamedResponse:
def qs(self) -> Querystring:
return Querystring(array_format="repeat")
+ @override
+ def _auth_headers(self, security: SecurityOptions) -> dict[str, str]:
+ headers: dict[str, str] = {}
+ if security.get("bearer_auth", False):
+ for key, value in self._bearer_auth.items():
+ headers.setdefault(key, value)
+ return headers
+
@override
def _custom_auth(self, security: SecurityOptions) -> httpx.Auth | None:
if security.get("o_auth2", False) and self._o_auth2 is not None:
return self._o_auth2
return None
+ @property
+ def _bearer_auth(self) -> dict[str, str]:
+ api_key = self.api_key
+ if api_key is None:
+ return {}
+ return {"Authorization": f"Bearer {api_key}"}
+
@property
def _o_auth2(self) -> httpx.Auth | None:
if self.client_id and self.client_secret:
@@ -377,6 +422,35 @@ def invitations(self) -> AsyncInvitationsResource:
return AsyncInvitationsResource(self)
+ @cached_property
+ def policy_bundle(self) -> AsyncPolicyBundleResource:
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+ from .resources.policy_bundle import AsyncPolicyBundleResource
+
+ return AsyncPolicyBundleResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncKeycardAPIWithRawResponse:
return AsyncKeycardAPIWithRawResponse(self)
@@ -390,12 +464,27 @@ def with_streaming_response(self) -> AsyncKeycardAPIWithStreamedResponse:
def qs(self) -> Querystring:
return Querystring(array_format="repeat")
+ @override
+ def _auth_headers(self, security: SecurityOptions) -> dict[str, str]:
+ headers: dict[str, str] = {}
+ if security.get("bearer_auth", False):
+ for key, value in self._bearer_auth.items():
+ headers.setdefault(key, value)
+ return headers
+
@override
def _custom_auth(self, security: SecurityOptions) -> httpx.Auth | None:
if security.get("o_auth2", False) and self._o_auth2 is not None:
return self._o_auth2
return None
+ @property
+ def _bearer_auth(self) -> dict[str, str]:
+ api_key = self.api_key
+ if api_key is None:
+ return {}
+ return {"Authorization": f"Bearer {api_key}"}
+
@property
def _o_auth2(self) -> httpx.Auth | None:
if self.client_id and self.client_secret:
@@ -538,6 +627,35 @@ def invitations(self) -> invitations.InvitationsResourceWithRawResponse:
return InvitationsResourceWithRawResponse(self._client.invitations)
+ @cached_property
+ def policy_bundle(self) -> policy_bundle.PolicyBundleResourceWithRawResponse:
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+ from .resources.policy_bundle import PolicyBundleResourceWithRawResponse
+
+ return PolicyBundleResourceWithRawResponse(self._client.policy_bundle)
+
class AsyncKeycardAPIWithRawResponse:
_client: AsyncKeycardAPI
@@ -563,6 +681,35 @@ def invitations(self) -> invitations.AsyncInvitationsResourceWithRawResponse:
return AsyncInvitationsResourceWithRawResponse(self._client.invitations)
+ @cached_property
+ def policy_bundle(self) -> policy_bundle.AsyncPolicyBundleResourceWithRawResponse:
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+ from .resources.policy_bundle import AsyncPolicyBundleResourceWithRawResponse
+
+ return AsyncPolicyBundleResourceWithRawResponse(self._client.policy_bundle)
+
class KeycardAPIWithStreamedResponse:
_client: KeycardAPI
@@ -588,6 +735,35 @@ def invitations(self) -> invitations.InvitationsResourceWithStreamingResponse:
return InvitationsResourceWithStreamingResponse(self._client.invitations)
+ @cached_property
+ def policy_bundle(self) -> policy_bundle.PolicyBundleResourceWithStreamingResponse:
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+ from .resources.policy_bundle import PolicyBundleResourceWithStreamingResponse
+
+ return PolicyBundleResourceWithStreamingResponse(self._client.policy_bundle)
+
class AsyncKeycardAPIWithStreamedResponse:
_client: AsyncKeycardAPI
@@ -613,6 +789,35 @@ def invitations(self) -> invitations.AsyncInvitationsResourceWithStreamingRespon
return AsyncInvitationsResourceWithStreamingResponse(self._client.invitations)
+ @cached_property
+ def policy_bundle(self) -> policy_bundle.AsyncPolicyBundleResourceWithStreamingResponse:
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+ from .resources.policy_bundle import AsyncPolicyBundleResourceWithStreamingResponse
+
+ return AsyncPolicyBundleResourceWithStreamingResponse(self._client.policy_bundle)
+
Client = KeycardAPI
diff --git a/src/keycardai_api/_models.py b/src/keycardai_api/_models.py
index 13d0794..90bf844 100644
--- a/src/keycardai_api/_models.py
+++ b/src/keycardai_api/_models.py
@@ -872,6 +872,7 @@ def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]:
class SecurityOptions(TypedDict, total=False):
+ bearer_auth: bool
o_auth2: bool
@@ -903,7 +904,10 @@ class FinalRequestOptions(pydantic.BaseModel):
idempotency_key: Union[str, None] = None
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
follow_redirects: Union[bool, None] = None
- security: SecurityOptions = {"o_auth2": True}
+ security: SecurityOptions = {
+ "bearer_auth": True,
+ "o_auth2": True,
+ }
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
diff --git a/src/keycardai_api/_version.py b/src/keycardai_api/_version.py
index ff81ceb..cb30031 100644
--- a/src/keycardai_api/_version.py
+++ b/src/keycardai_api/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "keycardai_api"
-__version__ = "0.16.0" # x-release-please-version
+__version__ = "0.17.0" # x-release-please-version
diff --git a/src/keycardai_api/resources/__init__.py b/src/keycardai_api/resources/__init__.py
index d7c6a99..3e222d8 100644
--- a/src/keycardai_api/resources/__init__.py
+++ b/src/keycardai_api/resources/__init__.py
@@ -24,6 +24,14 @@
OrganizationsResourceWithStreamingResponse,
AsyncOrganizationsResourceWithStreamingResponse,
)
+from .policy_bundle import (
+ PolicyBundleResource,
+ AsyncPolicyBundleResource,
+ PolicyBundleResourceWithRawResponse,
+ AsyncPolicyBundleResourceWithRawResponse,
+ PolicyBundleResourceWithStreamingResponse,
+ AsyncPolicyBundleResourceWithStreamingResponse,
+)
__all__ = [
"ZonesResource",
@@ -44,4 +52,10 @@
"AsyncInvitationsResourceWithRawResponse",
"InvitationsResourceWithStreamingResponse",
"AsyncInvitationsResourceWithStreamingResponse",
+ "PolicyBundleResource",
+ "AsyncPolicyBundleResource",
+ "PolicyBundleResourceWithRawResponse",
+ "AsyncPolicyBundleResourceWithRawResponse",
+ "PolicyBundleResourceWithStreamingResponse",
+ "AsyncPolicyBundleResourceWithStreamingResponse",
]
diff --git a/src/keycardai_api/resources/organizations/sso_connection.py b/src/keycardai_api/resources/organizations/sso_connection.py
index 4a8a44f..002dbed 100644
--- a/src/keycardai_api/resources/organizations/sso_connection.py
+++ b/src/keycardai_api/resources/organizations/sso_connection.py
@@ -104,7 +104,7 @@ def update(
client_id: str | Omit = omit,
client_secret: str | Omit = omit,
identifier: str | Omit = omit,
- protocols: Optional[SSOConnectionProtocolParam] | Omit = omit,
+ protocols: Optional[sso_connection_update_params.Protocols] | Omit = omit,
x_client_request_id: 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.
@@ -125,7 +125,8 @@ def update(
identifier: SSO provider identifier (e.g., issuer URL)
- protocols: Protocol configuration for SSO connection
+ protocols: Protocol configuration for an SSO connection update. Omit a protocol to leave it
+ unchanged.
extra_headers: Send extra headers
@@ -329,7 +330,7 @@ async def update(
client_id: str | Omit = omit,
client_secret: str | Omit = omit,
identifier: str | Omit = omit,
- protocols: Optional[SSOConnectionProtocolParam] | Omit = omit,
+ protocols: Optional[sso_connection_update_params.Protocols] | Omit = omit,
x_client_request_id: 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.
@@ -350,7 +351,8 @@ async def update(
identifier: SSO provider identifier (e.g., issuer URL)
- protocols: Protocol configuration for SSO connection
+ protocols: Protocol configuration for an SSO connection update. Omit a protocol to leave it
+ unchanged.
extra_headers: Send extra headers
diff --git a/src/keycardai_api/resources/policy_bundle.py b/src/keycardai_api/resources/policy_bundle.py
new file mode 100644
index 0000000..e68008f
--- /dev/null
+++ b/src/keycardai_api/resources/policy_bundle.py
@@ -0,0 +1,542 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+
+import httpx
+
+from .._files import read_file_content, async_read_file_content
+from .._types import (
+ Body,
+ Omit,
+ Query,
+ Headers,
+ NoneType,
+ NotGiven,
+ BinaryTypes,
+ FileContent,
+ AsyncBinaryTypes,
+ omit,
+ not_given,
+)
+from .._utils import strip_not_given
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ BinaryAPIResponse,
+ AsyncBinaryAPIResponse,
+ StreamedBinaryAPIResponse,
+ AsyncStreamedBinaryAPIResponse,
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ to_custom_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+ to_custom_streamed_response_wrapper,
+ async_to_custom_raw_response_wrapper,
+ async_to_custom_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+
+__all__ = ["PolicyBundleResource", "AsyncPolicyBundleResource"]
+
+
+class PolicyBundleResource(SyncAPIResource):
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> PolicyBundleResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/keycardai/keycard-python#accessing-raw-response-data-eg-headers
+ """
+ return PolicyBundleResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PolicyBundleResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/keycardai/keycard-python#with_streaming_response
+ """
+ return PolicyBundleResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ *,
+ if_none_match: str | Omit = omit,
+ x_client_request_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BinaryAPIResponse:
+ """
+ Returns the effective Policy Bundle for the user identified by the zone-issued
+ resource-scoped token. When no user-scope binding exists, one will be generated
+ from the default set.
+
+ The response body is a binary archive in the codec selected via the `Accept`
+ header. The only codec supported today is
+ `application/vnd.keycard.policy-bundle.v1+tar+gzip`. Clients SHOULD send an
+ explicit `Accept` header; absent one, the server defaults to the tar+gzip codec.
+
+ Supports conditional fetch via `If-None-Match`: when the supplied ETag matches
+ the current bundle, the server responds `304 Not Modified` with no body.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "If-None-Match": if_none_match,
+ "X-Client-Request-ID": x_client_request_id,
+ }
+ ),
+ **(extra_headers or {}),
+ }
+ return self._get(
+ "/policy/bundle",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=BinaryAPIResponse,
+ )
+
+ def update(
+ self,
+ body: FileContent | BinaryTypes,
+ *,
+ if_match: str | Omit = omit,
+ x_client_request_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BinaryAPIResponse:
+ """
+ Accepts an edited Policy Bundle archive and applies it as the active user-scope
+ PolicySetVersion for the calling user.
+
+ The user's policy set is seeded from the system-default policies on first
+ access, forked into customer-owned policies; a user bundle therefore contains
+ only customer-owned policies. Applying an edit creates a new version of the
+ affected policy, and a `new_policy` entry adds a further customer-owned policy.
+ Platform-owned catalog policies are never edited in place by this operation.
+
+ The request body codec is determined from `Content-Type`. The only codec
+ supported today is `application/vnd.keycard.policy-bundle.v1+tar+gzip`.
+
+ Supports optimistic concurrency via `If-Match`: when supplied, the server
+ applies the bundle only if the supplied ETag matches the current bundle ETag;
+ otherwise responds `412 Precondition Failed`.
+
+ On success the server returns the materialized bundle (in the same codec) and
+ its new `ETag`.
+
+ Args:
+ body: tar+gzip Policy Bundle archive. `manifest.json` is **required** (see
+ `PolicyBundleManifest`); `schema.cedarschema` is **optional and ignored** — the
+ server validates against its attested schema for `manifest.schema.version`. The
+ manifest's `policies[]` list is authoritative for the resulting set: each entry
+ must have a matching `policies/.cedar` (or, for a `new_policy` entry,
+ `policies/.cedar`) member, and a member with no manifest entry is
+ dropped. Only the `sha` fields are advisory and recomputed server-side.
+ Duplicate or unrecognized entries are rejected with `bundle_invalid`. See the
+ **PolicyBundle** tag for the layout.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "If-Match": if_match,
+ "X-Client-Request-ID": x_client_request_id,
+ }
+ ),
+ **(extra_headers or {}),
+ }
+ extra_headers = {"Content-Type": "application/octet-stream", **(extra_headers or {})}
+ return self._put(
+ "/policy/bundle",
+ content=read_file_content(body) if isinstance(body, os.PathLike) else body,
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=BinaryAPIResponse,
+ )
+
+ def reset(
+ self,
+ *,
+ x_client_request_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Archives the PolicySet for the calling user (if any), causing subsequent
+ `GET /policy/bundle` requests to fall back to the default user policies.
+ Idempotent: returns `204 No Content` even when no user-scope binding exists.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})}
+ return self._delete(
+ "/policy/bundle",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncPolicyBundleResource(AsyncAPIResource):
+ """Per-user Policy Bundle resource.
+
+ Allows clients (typically the Keycard CLI)
+ to GET, PUT, and DELETE the effective Policy Set for the calling user
+ on a zone. The bundle is encoded with a content-negotiated codec (currently
+ only `application/vnd.keycard.policy-bundle.v1+tar+gzip`).
+
+ ## Archive layout
+
+ The bundle is a gzip-compressed tar archive with this logical layout:
+
+ | Entry | Required on PUT | Notes |
+ |-------|-----------------|-------|
+ | `manifest.json` | **Yes** | See `PolicyBundleManifest`. The only source of the authoritative `schema.version`. |
+ | `schema.cedarschema` | No | Convenience snapshot of the Cedar schema. **Ignored on PUT** — the server validates policies against its own attested schema for `manifest.schema.version`. **Always present on GET.** |
+ | `policies/.cedar` | — | One Cedar policy per file; the filename stem is the policy's public ID. |
+
+ Decode rules: duplicate entries and unrecognized/nested entries are
+ rejected (`bundle_invalid`). On PUT the manifest's `sha` fields and
+ `policies[]` list are advisory — the server recomputes every digest from
+ the archived bytes and derives the policy set from the `policies/` files.
+ On GET every digest is authoritative.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPolicyBundleResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/keycardai/keycard-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncPolicyBundleResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPolicyBundleResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/keycardai/keycard-python#with_streaming_response
+ """
+ return AsyncPolicyBundleResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ *,
+ if_none_match: str | Omit = omit,
+ x_client_request_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncBinaryAPIResponse:
+ """
+ Returns the effective Policy Bundle for the user identified by the zone-issued
+ resource-scoped token. When no user-scope binding exists, one will be generated
+ from the default set.
+
+ The response body is a binary archive in the codec selected via the `Accept`
+ header. The only codec supported today is
+ `application/vnd.keycard.policy-bundle.v1+tar+gzip`. Clients SHOULD send an
+ explicit `Accept` header; absent one, the server defaults to the tar+gzip codec.
+
+ Supports conditional fetch via `If-None-Match`: when the supplied ETag matches
+ the current bundle, the server responds `304 Not Modified` with no body.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "If-None-Match": if_none_match,
+ "X-Client-Request-ID": x_client_request_id,
+ }
+ ),
+ **(extra_headers or {}),
+ }
+ return await self._get(
+ "/policy/bundle",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=AsyncBinaryAPIResponse,
+ )
+
+ async def update(
+ self,
+ body: FileContent | AsyncBinaryTypes,
+ *,
+ if_match: str | Omit = omit,
+ x_client_request_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncBinaryAPIResponse:
+ """
+ Accepts an edited Policy Bundle archive and applies it as the active user-scope
+ PolicySetVersion for the calling user.
+
+ The user's policy set is seeded from the system-default policies on first
+ access, forked into customer-owned policies; a user bundle therefore contains
+ only customer-owned policies. Applying an edit creates a new version of the
+ affected policy, and a `new_policy` entry adds a further customer-owned policy.
+ Platform-owned catalog policies are never edited in place by this operation.
+
+ The request body codec is determined from `Content-Type`. The only codec
+ supported today is `application/vnd.keycard.policy-bundle.v1+tar+gzip`.
+
+ Supports optimistic concurrency via `If-Match`: when supplied, the server
+ applies the bundle only if the supplied ETag matches the current bundle ETag;
+ otherwise responds `412 Precondition Failed`.
+
+ On success the server returns the materialized bundle (in the same codec) and
+ its new `ETag`.
+
+ Args:
+ body: tar+gzip Policy Bundle archive. `manifest.json` is **required** (see
+ `PolicyBundleManifest`); `schema.cedarschema` is **optional and ignored** — the
+ server validates against its attested schema for `manifest.schema.version`. The
+ manifest's `policies[]` list is authoritative for the resulting set: each entry
+ must have a matching `policies/.cedar` (or, for a `new_policy` entry,
+ `policies/.cedar`) member, and a member with no manifest entry is
+ dropped. Only the `sha` fields are advisory and recomputed server-side.
+ Duplicate or unrecognized entries are rejected with `bundle_invalid`. See the
+ **PolicyBundle** tag for the layout.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "If-Match": if_match,
+ "X-Client-Request-ID": x_client_request_id,
+ }
+ ),
+ **(extra_headers or {}),
+ }
+ extra_headers = {"Content-Type": "application/octet-stream", **(extra_headers or {})}
+ return await self._put(
+ "/policy/bundle",
+ content=await async_read_file_content(body) if isinstance(body, os.PathLike) else body,
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=AsyncBinaryAPIResponse,
+ )
+
+ async def reset(
+ self,
+ *,
+ x_client_request_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Archives the PolicySet for the calling user (if any), causing subsequent
+ `GET /policy/bundle` requests to fall back to the default user policies.
+ Idempotent: returns `204 No Content` even when no user-scope binding exists.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})}
+ return await self._delete(
+ "/policy/bundle",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=NoneType,
+ )
+
+
+class PolicyBundleResourceWithRawResponse:
+ def __init__(self, policy_bundle: PolicyBundleResource) -> None:
+ self._policy_bundle = policy_bundle
+
+ self.retrieve = to_custom_raw_response_wrapper(
+ policy_bundle.retrieve,
+ BinaryAPIResponse,
+ )
+ self.update = to_custom_raw_response_wrapper(
+ policy_bundle.update,
+ BinaryAPIResponse,
+ )
+ self.reset = to_raw_response_wrapper(
+ policy_bundle.reset,
+ )
+
+
+class AsyncPolicyBundleResourceWithRawResponse:
+ def __init__(self, policy_bundle: AsyncPolicyBundleResource) -> None:
+ self._policy_bundle = policy_bundle
+
+ self.retrieve = async_to_custom_raw_response_wrapper(
+ policy_bundle.retrieve,
+ AsyncBinaryAPIResponse,
+ )
+ self.update = async_to_custom_raw_response_wrapper(
+ policy_bundle.update,
+ AsyncBinaryAPIResponse,
+ )
+ self.reset = async_to_raw_response_wrapper(
+ policy_bundle.reset,
+ )
+
+
+class PolicyBundleResourceWithStreamingResponse:
+ def __init__(self, policy_bundle: PolicyBundleResource) -> None:
+ self._policy_bundle = policy_bundle
+
+ self.retrieve = to_custom_streamed_response_wrapper(
+ policy_bundle.retrieve,
+ StreamedBinaryAPIResponse,
+ )
+ self.update = to_custom_streamed_response_wrapper(
+ policy_bundle.update,
+ StreamedBinaryAPIResponse,
+ )
+ self.reset = to_streamed_response_wrapper(
+ policy_bundle.reset,
+ )
+
+
+class AsyncPolicyBundleResourceWithStreamingResponse:
+ def __init__(self, policy_bundle: AsyncPolicyBundleResource) -> None:
+ self._policy_bundle = policy_bundle
+
+ self.retrieve = async_to_custom_streamed_response_wrapper(
+ policy_bundle.retrieve,
+ AsyncStreamedBinaryAPIResponse,
+ )
+ self.update = async_to_custom_streamed_response_wrapper(
+ policy_bundle.update,
+ AsyncStreamedBinaryAPIResponse,
+ )
+ self.reset = async_to_streamed_response_wrapper(
+ policy_bundle.reset,
+ )
diff --git a/src/keycardai_api/resources/zones/policy_schemas.py b/src/keycardai_api/resources/zones/policy_schemas.py
index eb5b9fd..a5a4f5b 100644
--- a/src/keycardai_api/resources/zones/policy_schemas.py
+++ b/src/keycardai_api/resources/zones/policy_schemas.py
@@ -36,7 +36,7 @@ class PolicySchemasResource(SyncAPIResource):
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
@@ -284,7 +284,7 @@ class AsyncPolicySchemasResource(AsyncAPIResource):
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
diff --git a/src/keycardai_api/resources/zones/providers.py b/src/keycardai_api/resources/zones/providers.py
index d4ed74f..ac7f8ca 100644
--- a/src/keycardai_api/resources/zones/providers.py
+++ b/src/keycardai_api/resources/zones/providers.py
@@ -54,7 +54,7 @@ def create(
client_id: str | Omit = omit,
client_secret: str | Omit = omit,
description: Optional[str] | Omit = omit,
- metadata: object | Omit = omit,
+ metadata: provider_create_params.Metadata | Omit = omit,
protocols: provider_create_params.Protocols | 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.
@@ -160,7 +160,7 @@ def update(
client_secret: Optional[str] | Omit = omit,
description: Optional[str] | Omit = omit,
identifier: str | Omit = omit,
- metadata: Optional[object] | Omit = omit,
+ metadata: Optional[provider_update_params.Metadata] | Omit = omit,
name: str | Omit = omit,
protocols: Optional[provider_update_params.Protocols] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -354,7 +354,7 @@ async def create(
client_id: str | Omit = omit,
client_secret: str | Omit = omit,
description: Optional[str] | Omit = omit,
- metadata: object | Omit = omit,
+ metadata: provider_create_params.Metadata | Omit = omit,
protocols: provider_create_params.Protocols | 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.
@@ -460,7 +460,7 @@ async def update(
client_secret: Optional[str] | Omit = omit,
description: Optional[str] | Omit = omit,
identifier: str | Omit = omit,
- metadata: Optional[object] | Omit = omit,
+ metadata: Optional[provider_update_params.Metadata] | Omit = omit,
name: str | Omit = omit,
protocols: Optional[provider_update_params.Protocols] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
diff --git a/src/keycardai_api/resources/zones/resources.py b/src/keycardai_api/resources/zones/resources.py
index 2941cfe..0f274e8 100644
--- a/src/keycardai_api/resources/zones/resources.py
+++ b/src/keycardai_api/resources/zones/resources.py
@@ -271,9 +271,13 @@ def list(
before: str | Omit = omit,
credential_provider_id: str | Omit = omit,
expand: Union[Literal["total_count"], List[Literal["total_count"]]] | Omit = omit,
+ filter_identifier: Union[str, SequenceNotStr[str]] | Omit = omit,
+ filter_owner_type: Literal["platform", "customer"] | Omit = omit,
identifier: str | Omit = omit,
limit: int | Omit = omit,
slug: str | Omit = omit,
+ traits: List[Literal["external", "proxy", "mcp-server"]] | Omit = omit,
+ traits_all: List[Literal["external", "proxy", "mcp-server"]] | 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,
@@ -281,8 +285,15 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ResourceListResponse:
- """
- Returns a list of resources in the specified zone
+ """Returns a paginated list of resources in the specified zone.
+
+ Use cursor
+ pagination via `after`/`before`, and `expand[]=total_count` to include the
+ matching row count. Filter by exact identifier via `filter[identifier]`. Filter
+ by trait via `traits[all]` (AND, all listed) or `traits[]` (OR, any), each
+ repeatable. The scalar `identifier` query parameter is a backward-compatible
+ alias for `filter[identifier]`: exact match on a single value, folded into the
+ same exact-match identifier filter.
Args:
after: Cursor for forward pagination
@@ -291,10 +302,21 @@ def list(
credential_provider_id: Filter resources by credential provider ID
- identifier: Filter resources by identifier
+ filter_identifier: Filter by exact resource identifier
+
+ filter_owner_type: Filter by owner type: `platform` (Keycard-managed) or `customer` (org-created).
+
+ identifier: Backward-compatible alias for `filter[identifier]`: exact match on a single
+ resource identifier.
limit: Maximum number of items to return
+ traits: Filter by traits (OR matching — returns resources with any of the specified
+ traits)
+
+ traits_all: Filter by traits (AND matching - returns resources with all of the specified
+ traits)
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -318,9 +340,13 @@ def list(
"before": before,
"credential_provider_id": credential_provider_id,
"expand": expand,
+ "filter_identifier": filter_identifier,
+ "filter_owner_type": filter_owner_type,
"identifier": identifier,
"limit": limit,
"slug": slug,
+ "traits": traits,
+ "traits_all": traits_all,
},
resource_list_params.ResourceListParams,
),
@@ -606,9 +632,13 @@ async def list(
before: str | Omit = omit,
credential_provider_id: str | Omit = omit,
expand: Union[Literal["total_count"], List[Literal["total_count"]]] | Omit = omit,
+ filter_identifier: Union[str, SequenceNotStr[str]] | Omit = omit,
+ filter_owner_type: Literal["platform", "customer"] | Omit = omit,
identifier: str | Omit = omit,
limit: int | Omit = omit,
slug: str | Omit = omit,
+ traits: List[Literal["external", "proxy", "mcp-server"]] | Omit = omit,
+ traits_all: List[Literal["external", "proxy", "mcp-server"]] | 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,
@@ -616,8 +646,15 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ResourceListResponse:
- """
- Returns a list of resources in the specified zone
+ """Returns a paginated list of resources in the specified zone.
+
+ Use cursor
+ pagination via `after`/`before`, and `expand[]=total_count` to include the
+ matching row count. Filter by exact identifier via `filter[identifier]`. Filter
+ by trait via `traits[all]` (AND, all listed) or `traits[]` (OR, any), each
+ repeatable. The scalar `identifier` query parameter is a backward-compatible
+ alias for `filter[identifier]`: exact match on a single value, folded into the
+ same exact-match identifier filter.
Args:
after: Cursor for forward pagination
@@ -626,10 +663,21 @@ async def list(
credential_provider_id: Filter resources by credential provider ID
- identifier: Filter resources by identifier
+ filter_identifier: Filter by exact resource identifier
+
+ filter_owner_type: Filter by owner type: `platform` (Keycard-managed) or `customer` (org-created).
+
+ identifier: Backward-compatible alias for `filter[identifier]`: exact match on a single
+ resource identifier.
limit: Maximum number of items to return
+ traits: Filter by traits (OR matching — returns resources with any of the specified
+ traits)
+
+ traits_all: Filter by traits (AND matching - returns resources with all of the specified
+ traits)
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -653,9 +701,13 @@ async def list(
"before": before,
"credential_provider_id": credential_provider_id,
"expand": expand,
+ "filter_identifier": filter_identifier,
+ "filter_owner_type": filter_owner_type,
"identifier": identifier,
"limit": limit,
"slug": slug,
+ "traits": traits,
+ "traits_all": traits_all,
},
resource_list_params.ResourceListParams,
),
diff --git a/src/keycardai_api/resources/zones/users.py b/src/keycardai_api/resources/zones/users.py
index 81cd5a1..52b71d8 100644
--- a/src/keycardai_api/resources/zones/users.py
+++ b/src/keycardai_api/resources/zones/users.py
@@ -88,12 +88,24 @@ def list(
after: str | Omit = omit,
before: str | Omit = omit,
expand: Union[
- Literal["total_count", "session_count", "grant_count", "role-assignments"],
- List[Literal["total_count", "session_count", "grant_count", "role-assignments"]],
+ Literal[
+ "total_count", "session_count", "grant_count", "role-assignments", "credentials", "credentials.provider"
+ ],
+ List[
+ Literal[
+ "total_count",
+ "session_count",
+ "grant_count",
+ "role-assignments",
+ "credentials",
+ "credentials.provider",
+ ]
+ ],
]
| Omit = omit,
filter_email: Union[str, SequenceNotStr[str]] | Omit = omit,
filter_id: Union[str, SequenceNotStr[str]] | Omit = omit,
+ filter_identifier: Union[str, SequenceNotStr[str]] | Omit = omit,
limit: int | Omit = omit,
query: Union[str, SequenceNotStr[str]] | Omit = omit,
query_email: Union[str, SequenceNotStr[str]] | Omit = omit,
@@ -109,27 +121,29 @@ def list(
"""
Returns a list of users in the specified zone.
- **Rollout note:** the paginated/searchable/sortable behavior described below is
- gated behind the `user-pagination` feature flag and is currently disabled for
- most zones. While the flag is off, the response returns every user in the zone
- (capped at 100) in `items` and a fixed pagination envelope where `after_cursor`
- and `before_cursor` are `null` and `total_count` is `0`. The query parameters
- below are accepted but ignored. The flag is rolled out per-zone in Datadog and
- will become the default once Console adopts the paginated contract.
+ Note: cursor pagination, search, and sort are not yet enabled for all zones.
+ Where they are not enabled, the response returns all users in the zone (capped
+ at 100) in `items`, with `after_cursor` and `before_cursor` set to `null` and
+ `total_count` of `0`; `filter[email]` and `filter[identifier]` are still
+ applied, while the pagination, search, and sort parameters below are accepted
+ but ignored.
Use cursor pagination via `after`/`before`. Sort: comma-separated field list;
prefix with `-` for descending. Use `expand[]=total_count` to include the
matching row count, `expand[]=session_count` to include per-user session counts,
- `expand[]=grant_count` to include per-user delegated-grant counts, and
- `expand[]=role-assignments` to include each user's structured role grants.
- Filter by exact email via `filter[email]`; search via `query[email]` /
- `query[subject]` / `query[]` (substring match, OR'd across repeated values).
- `query[]` matches against email and federation credential subject. Pass
- `filter[id]` (repeatable, max 100) to restrict results to a known set of users —
- mutually exclusive with `after`/`before` (returns 400 if combined). When
- `filter[id]` is set, `limit` is ignored and the response contains every
- requested user that exists in the zone, in a single page. IDs not in the zone
- are silently omitted.
+ `expand[]=grant_count` to include per-user delegated-grant counts,
+ `expand[]=role-assignments` to include each user's structured role grants,
+ `expand[]=credentials` to include each user's authentication credentials (each
+ with its `provider_id`), and `expand[]=credentials.provider` to additionally
+ inline the full identity provider on each federation credential. Filter by exact
+ email via `filter[email]` and by exact identifier via `filter[identifier]`;
+ search via `query[email]` / `query[subject]` / `query[]` (substring match, OR'd
+ across repeated values). `query[]` matches against email and federation
+ credential subject. Pass `filter[id]` (repeatable, max 100) to restrict results
+ to a known set of users — mutually exclusive with `after`/`before` (returns 400
+ if combined). When `filter[id]` is set, `limit` is ignored and the response
+ contains every requested user that exists in the zone, in a single page. IDs not
+ in the zone are silently omitted.
Args:
after: Cursor for forward pagination
@@ -141,6 +155,8 @@ def list(
filter_id: Restrict results to users with this publicId. Repeatable, max 100. Mutually
exclusive with after/before.
+ filter_identifier: Filter by exact user identifier
+
limit: Maximum number of items to return
query: Search across email and credential subject (substring match)
@@ -176,6 +192,7 @@ def list(
"expand": expand,
"filter_email": filter_email,
"filter_id": filter_id,
+ "filter_identifier": filter_identifier,
"limit": limit,
"query": query,
"query_email": query_email,
@@ -252,12 +269,24 @@ async def list(
after: str | Omit = omit,
before: str | Omit = omit,
expand: Union[
- Literal["total_count", "session_count", "grant_count", "role-assignments"],
- List[Literal["total_count", "session_count", "grant_count", "role-assignments"]],
+ Literal[
+ "total_count", "session_count", "grant_count", "role-assignments", "credentials", "credentials.provider"
+ ],
+ List[
+ Literal[
+ "total_count",
+ "session_count",
+ "grant_count",
+ "role-assignments",
+ "credentials",
+ "credentials.provider",
+ ]
+ ],
]
| Omit = omit,
filter_email: Union[str, SequenceNotStr[str]] | Omit = omit,
filter_id: Union[str, SequenceNotStr[str]] | Omit = omit,
+ filter_identifier: Union[str, SequenceNotStr[str]] | Omit = omit,
limit: int | Omit = omit,
query: Union[str, SequenceNotStr[str]] | Omit = omit,
query_email: Union[str, SequenceNotStr[str]] | Omit = omit,
@@ -273,27 +302,29 @@ async def list(
"""
Returns a list of users in the specified zone.
- **Rollout note:** the paginated/searchable/sortable behavior described below is
- gated behind the `user-pagination` feature flag and is currently disabled for
- most zones. While the flag is off, the response returns every user in the zone
- (capped at 100) in `items` and a fixed pagination envelope where `after_cursor`
- and `before_cursor` are `null` and `total_count` is `0`. The query parameters
- below are accepted but ignored. The flag is rolled out per-zone in Datadog and
- will become the default once Console adopts the paginated contract.
+ Note: cursor pagination, search, and sort are not yet enabled for all zones.
+ Where they are not enabled, the response returns all users in the zone (capped
+ at 100) in `items`, with `after_cursor` and `before_cursor` set to `null` and
+ `total_count` of `0`; `filter[email]` and `filter[identifier]` are still
+ applied, while the pagination, search, and sort parameters below are accepted
+ but ignored.
Use cursor pagination via `after`/`before`. Sort: comma-separated field list;
prefix with `-` for descending. Use `expand[]=total_count` to include the
matching row count, `expand[]=session_count` to include per-user session counts,
- `expand[]=grant_count` to include per-user delegated-grant counts, and
- `expand[]=role-assignments` to include each user's structured role grants.
- Filter by exact email via `filter[email]`; search via `query[email]` /
- `query[subject]` / `query[]` (substring match, OR'd across repeated values).
- `query[]` matches against email and federation credential subject. Pass
- `filter[id]` (repeatable, max 100) to restrict results to a known set of users —
- mutually exclusive with `after`/`before` (returns 400 if combined). When
- `filter[id]` is set, `limit` is ignored and the response contains every
- requested user that exists in the zone, in a single page. IDs not in the zone
- are silently omitted.
+ `expand[]=grant_count` to include per-user delegated-grant counts,
+ `expand[]=role-assignments` to include each user's structured role grants,
+ `expand[]=credentials` to include each user's authentication credentials (each
+ with its `provider_id`), and `expand[]=credentials.provider` to additionally
+ inline the full identity provider on each federation credential. Filter by exact
+ email via `filter[email]` and by exact identifier via `filter[identifier]`;
+ search via `query[email]` / `query[subject]` / `query[]` (substring match, OR'd
+ across repeated values). `query[]` matches against email and federation
+ credential subject. Pass `filter[id]` (repeatable, max 100) to restrict results
+ to a known set of users — mutually exclusive with `after`/`before` (returns 400
+ if combined). When `filter[id]` is set, `limit` is ignored and the response
+ contains every requested user that exists in the zone, in a single page. IDs not
+ in the zone are silently omitted.
Args:
after: Cursor for forward pagination
@@ -305,6 +336,8 @@ async def list(
filter_id: Restrict results to users with this publicId. Repeatable, max 100. Mutually
exclusive with after/before.
+ filter_identifier: Filter by exact user identifier
+
limit: Maximum number of items to return
query: Search across email and credential subject (substring match)
@@ -340,6 +373,7 @@ async def list(
"expand": expand,
"filter_email": filter_email,
"filter_id": filter_id,
+ "filter_identifier": filter_identifier,
"limit": limit,
"query": query,
"query_email": query_email,
diff --git a/src/keycardai_api/resources/zones/zones.py b/src/keycardai_api/resources/zones/zones.py
index fcbdb8a..aef2478 100644
--- a/src/keycardai_api/resources/zones/zones.py
+++ b/src/keycardai_api/resources/zones/zones.py
@@ -188,7 +188,7 @@ def policy_schemas(self) -> PolicySchemasResource:
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
@@ -556,7 +556,7 @@ def policy_schemas(self) -> AsyncPolicySchemasResource:
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
@@ -943,7 +943,7 @@ def policy_schemas(self) -> PolicySchemasResourceWithRawResponse:
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
@@ -1034,7 +1034,7 @@ def policy_schemas(self) -> AsyncPolicySchemasResourceWithRawResponse:
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
@@ -1125,7 +1125,7 @@ def policy_schemas(self) -> PolicySchemasResourceWithStreamingResponse:
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
@@ -1216,7 +1216,7 @@ def policy_schemas(self) -> AsyncPolicySchemasResourceWithStreamingResponse:
- **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"`
- **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"`
- **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String)
- - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields
+ - **Keycard::Claims** — `email` (String), `groups` (Set of String), `issuer_claims` (issuer-specific claims record), plus arbitrary additional fields
Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+).
In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`.
diff --git a/src/keycardai_api/types/__init__.py b/src/keycardai_api/types/__init__.py
index d89e8f9..b64078b 100644
--- a/src/keycardai_api/types/__init__.py
+++ b/src/keycardai_api/types/__init__.py
@@ -18,6 +18,7 @@
from .organization_create_params import OrganizationCreateParams as OrganizationCreateParams
from .organization_list_response import OrganizationListResponse as OrganizationListResponse
from .organization_update_params import OrganizationUpdateParams as OrganizationUpdateParams
+from .policy_bundle_update_params import PolicyBundleUpdateParams as PolicyBundleUpdateParams
from .invitation_retrieve_response import InvitationRetrieveResponse as InvitationRetrieveResponse
from .organization_retrieve_params import OrganizationRetrieveParams as OrganizationRetrieveParams
from .encryption_key_aws_kms_config import EncryptionKeyAwsKmsConfig as EncryptionKeyAwsKmsConfig
diff --git a/src/keycardai_api/types/organizations/sso_connection_protocol.py b/src/keycardai_api/types/organizations/sso_connection_protocol.py
index 5767f80..42ae9b7 100644
--- a/src/keycardai_api/types/organizations/sso_connection_protocol.py
+++ b/src/keycardai_api/types/organizations/sso_connection_protocol.py
@@ -1,6 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import Dict, List, Optional
from ..._models import BaseModel
@@ -13,6 +13,13 @@ class Oauth2(BaseModel):
authorization_endpoint: Optional[str] = None
"""OAuth 2.0 authorization endpoint"""
+ authorization_parameters: Optional[Dict[str, str]] = None
+ """Custom query parameters appended to authorization redirect URLs.
+
+ Use for non-standard providers (e.g. Google prompt=consent,
+ access_type=offline).
+ """
+
code_challenge_methods_supported: Optional[List[str]] = None
"""Supported PKCE code challenge methods"""
@@ -32,6 +39,18 @@ class Oauth2(BaseModel):
class Openid(BaseModel):
"""OpenID Connect protocol configuration for SSO connection"""
+ scopes: Optional[List[str]] = None
+ """Additional OIDC scopes to request from this provider during authentication (e.g.
+
+ "groups"). Merged with the default scopes (openid, profile, email).
+ """
+
+ user_identifier_claim: Optional[str] = None
+ """
+ Name of a top-level string claim in the provider's ID Token to use as the user
+ identifier on user creation. When not set, the user's Keycard ID is used.
+ """
+
userinfo_endpoint: Optional[str] = None
"""OpenID Connect UserInfo endpoint"""
diff --git a/src/keycardai_api/types/organizations/sso_connection_protocol_param.py b/src/keycardai_api/types/organizations/sso_connection_protocol_param.py
index 84d6074..2183279 100644
--- a/src/keycardai_api/types/organizations/sso_connection_protocol_param.py
+++ b/src/keycardai_api/types/organizations/sso_connection_protocol_param.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Dict, Optional
from typing_extensions import TypedDict
from ..._types import SequenceNotStr
@@ -16,6 +16,13 @@ class Oauth2(TypedDict, total=False):
authorization_endpoint: Optional[str]
"""OAuth 2.0 authorization endpoint"""
+ authorization_parameters: Optional[Dict[str, str]]
+ """Custom query parameters appended to authorization redirect URLs.
+
+ Use for non-standard providers (e.g. Google prompt=consent,
+ access_type=offline).
+ """
+
code_challenge_methods_supported: Optional[SequenceNotStr[str]]
"""Supported PKCE code challenge methods"""
@@ -35,6 +42,18 @@ class Oauth2(TypedDict, total=False):
class Openid(TypedDict, total=False):
"""OpenID Connect protocol configuration for SSO connection"""
+ scopes: Optional[SequenceNotStr[str]]
+ """Additional OIDC scopes to request from this provider during authentication (e.g.
+
+ "groups"). Merged with the default scopes (openid, profile, email).
+ """
+
+ user_identifier_claim: Optional[str]
+ """
+ Name of a top-level string claim in the provider's ID Token to use as the user
+ identifier on user creation. When not set, the user's Keycard ID is used.
+ """
+
userinfo_endpoint: Optional[str]
"""OpenID Connect UserInfo endpoint"""
diff --git a/src/keycardai_api/types/organizations/sso_connection_update_params.py b/src/keycardai_api/types/organizations/sso_connection_update_params.py
index e8d852e..93c8454 100644
--- a/src/keycardai_api/types/organizations/sso_connection_update_params.py
+++ b/src/keycardai_api/types/organizations/sso_connection_update_params.py
@@ -2,13 +2,13 @@
from __future__ import annotations
-from typing import Optional
+from typing import Dict, Optional
from typing_extensions import Annotated, TypedDict
+from ..._types import SequenceNotStr
from ..._utils import PropertyInfo
-from .sso_connection_protocol_param import SSOConnectionProtocolParam
-__all__ = ["SSOConnectionUpdateParams"]
+__all__ = ["SSOConnectionUpdateParams", "Protocols", "ProtocolsOauth2", "ProtocolsOpenid"]
class SSOConnectionUpdateParams(TypedDict, total=False):
@@ -21,7 +21,86 @@ class SSOConnectionUpdateParams(TypedDict, total=False):
identifier: str
"""SSO provider identifier (e.g., issuer URL)"""
- protocols: Optional[SSOConnectionProtocolParam]
- """Protocol configuration for SSO connection"""
+ protocols: Optional[Protocols]
+ """Protocol configuration for an SSO connection update.
+
+ Omit a protocol to leave it unchanged.
+ """
x_client_request_id: Annotated[str, PropertyInfo(alias="X-Client-Request-ID")]
+
+
+class ProtocolsOauth2(TypedDict, total=False):
+ """OAuth 2.0 protocol configuration for an SSO connection update.
+
+ Each field is tri-state, omit to leave unchanged, send null to clear, send a value to set.
+ """
+
+ authorization_endpoint: Optional[str]
+ """OAuth 2.0 authorization endpoint. Set to null to clear."""
+
+ authorization_parameters: Optional[Dict[str, str]]
+ """Custom query parameters appended to authorization redirect URLs.
+
+ Use for non-standard providers (e.g. Google prompt=consent,
+ access_type=offline). Set to null to clear.
+ """
+
+ code_challenge_methods_supported: Optional[SequenceNotStr[str]]
+ """Supported PKCE code challenge methods. Set to null to clear."""
+
+ jwks_uri: Optional[str]
+ """JSON Web Key Set endpoint. Set to null to clear."""
+
+ registration_endpoint: Optional[str]
+ """OAuth 2.0 registration endpoint. Set to null to clear."""
+
+ scopes_supported: Optional[SequenceNotStr[str]]
+ """Supported OAuth 2.0 scopes. Set to null to clear."""
+
+ token_endpoint: Optional[str]
+ """OAuth 2.0 token endpoint. Set to null to clear."""
+
+
+class ProtocolsOpenid(TypedDict, total=False):
+ """OpenID Connect protocol configuration for an SSO connection update.
+
+ Each field is tri-state, omit to leave unchanged, send null to clear, send a value to set.
+ """
+
+ scopes: Optional[SequenceNotStr[str]]
+ """Additional OIDC scopes to request from this provider during authentication (e.g.
+
+ "groups"). Merged with the default scopes (openid, profile, email). Set to null
+ to clear.
+ """
+
+ user_identifier_claim: Optional[str]
+ """
+ Name of a top-level string claim in the provider's ID Token to use as the user
+ identifier on user creation. Set to null to clear.
+ """
+
+ userinfo_endpoint: Optional[str]
+ """OpenID Connect UserInfo endpoint. Set to null to clear."""
+
+
+class Protocols(TypedDict, total=False):
+ """Protocol configuration for an SSO connection update.
+
+ Omit a protocol to leave it unchanged.
+ """
+
+ oauth2: Optional[ProtocolsOauth2]
+ """OAuth 2.0 protocol configuration for an SSO connection update.
+
+ Each field is tri-state, omit to leave unchanged, send null to clear, send a
+ value to set.
+ """
+
+ openid: Optional[ProtocolsOpenid]
+ """OpenID Connect protocol configuration for an SSO connection update.
+
+ Each field is tri-state, omit to leave unchanged, send null to clear, send a
+ value to set.
+ """
diff --git a/src/keycardai_api/types/policy_bundle_update_params.py b/src/keycardai_api/types/policy_bundle_update_params.py
new file mode 100644
index 0000000..86dd8e2
--- /dev/null
+++ b/src/keycardai_api/types/policy_bundle_update_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["PolicyBundleUpdateParams"]
+
+
+class PolicyBundleUpdateParams(TypedDict, total=False):
+ if_match: Annotated[str, PropertyInfo(alias="If-Match")]
+
+ x_client_request_id: Annotated[str, PropertyInfo(alias="X-Client-Request-ID")]
diff --git a/src/keycardai_api/types/zones/__init__.py b/src/keycardai_api/types/zones/__init__.py
index 03942fa..283dad0 100644
--- a/src/keycardai_api/types/zones/__init__.py
+++ b/src/keycardai_api/types/zones/__init__.py
@@ -20,12 +20,9 @@
from .user_agent import UserAgent as UserAgent
from .application import Application as Application
from .base_fields import BaseFields as BaseFields
-from .task_status import TaskStatus as TaskStatus
from .zone_member import ZoneMember as ZoneMember
-from .install_status import InstallStatus as InstallStatus
from .metadata_param import MetadataParam as MetadataParam
from .schema_version import SchemaVersion as SchemaVersion
-from .task_operation import TaskOperation as TaskOperation
from .user_list_params import UserListParams as UserListParams
from .application_trait import ApplicationTrait as ApplicationTrait
from .member_add_params import MemberAddParams as MemberAddParams
diff --git a/src/keycardai_api/types/zones/application_trait.py b/src/keycardai_api/types/zones/application_trait.py
index 293bb0f..c266be7 100644
--- a/src/keycardai_api/types/zones/application_trait.py
+++ b/src/keycardai_api/types/zones/application_trait.py
@@ -4,4 +4,4 @@
__all__ = ["ApplicationTrait"]
-ApplicationTrait: TypeAlias = Literal["gateway", "mcp-provider"]
+ApplicationTrait: TypeAlias = Literal["gateway", "mcp-provider", "unified-gateway", "mcp-server"]
diff --git a/src/keycardai_api/types/zones/install_status.py b/src/keycardai_api/types/zones/install_status.py
deleted file mode 100644
index 835b898..0000000
--- a/src/keycardai_api/types/zones/install_status.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing_extensions import Literal, TypeAlias
-
-__all__ = ["InstallStatus"]
-
-InstallStatus: TypeAlias = Literal["pending", "active", "deleting", "failed", "deleted"]
diff --git a/src/keycardai_api/types/zones/packages/__init__.py b/src/keycardai_api/types/zones/packages/__init__.py
deleted file mode 100644
index f8ee8b1..0000000
--- a/src/keycardai_api/types/zones/packages/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
diff --git a/src/keycardai_api/types/zones/policies/policy_version.py b/src/keycardai_api/types/zones/policies/policy_version.py
index f9982be..d8683af 100644
--- a/src/keycardai_api/types/zones/policies/policy_version.py
+++ b/src/keycardai_api/types/zones/policies/policy_version.py
@@ -43,7 +43,9 @@ class PolicyVersion(BaseModel):
"""Cedar policy in JSON representation.
Populated by default and when `format=json` is passed; null when `format=cedar`
- narrows the response to the text representation only.
+ narrows the response to the text representation only. Serialized verbatim from
+ the stored Cedar so the order of `staticPolicies` matches the source policy
+ order (ACC-613).
"""
cedar_raw: Optional[str] = None
diff --git a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py
index 5b89efc..996c5e2 100644
--- a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py
+++ b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py
@@ -41,11 +41,23 @@ class PolicySetVersion(BaseModel):
version: int
active: Optional[bool] = None
- """Whether this policy set version is currently bound with mode='active'"""
+ """Whether this policy set version is currently bound with mode='active'.
+
+ Always populated in responses; clients must treat absence as unknown rather than
+ inferring 'not bound'.
+ """
archived_at: Optional[datetime] = None
+ """Timestamp when the version was archived.
+
+ Non-null only for archived versions; null or absent means not archived.
+ """
archived_by: Optional[str] = None
+ """Identifier of the actor that archived the version.
+
+ Null or absent means not archived.
+ """
attestation: Optional[AttestationStatement] = None
"""Decoded content of an Attestation JWS payload.
diff --git a/src/keycardai_api/types/zones/provider.py b/src/keycardai_api/types/zones/provider.py
index 48f9a9d..e886719 100644
--- a/src/keycardai_api/types/zones/provider.py
+++ b/src/keycardai_api/types/zones/provider.py
@@ -6,7 +6,14 @@
from ..._models import BaseModel
-__all__ = ["Provider", "Protocols", "ProtocolsOauth2", "ProtocolsOpenid"]
+__all__ = ["Provider", "Metadata", "Protocols", "ProtocolsOauth2", "ProtocolsOpenid"]
+
+
+class Metadata(BaseModel):
+ """Provider metadata"""
+
+ icon_url: Optional[str] = None
+ """Icon URL"""
class ProtocolsOauth2(BaseModel):
@@ -137,7 +144,7 @@ class Provider(BaseModel):
description: Optional[str] = None
"""Human-readable description"""
- metadata: Optional[object] = None
+ metadata: Optional[Metadata] = None
"""Provider metadata"""
protocols: Optional[Protocols] = None
diff --git a/src/keycardai_api/types/zones/provider_create_params.py b/src/keycardai_api/types/zones/provider_create_params.py
index ebc66b7..66a6a1f 100644
--- a/src/keycardai_api/types/zones/provider_create_params.py
+++ b/src/keycardai_api/types/zones/provider_create_params.py
@@ -7,7 +7,7 @@
from ..._types import SequenceNotStr
-__all__ = ["ProviderCreateParams", "Protocols", "ProtocolsOauth2", "ProtocolsOpenid"]
+__all__ = ["ProviderCreateParams", "Metadata", "Protocols", "ProtocolsOauth2", "ProtocolsOpenid"]
class ProviderCreateParams(TypedDict, total=False):
@@ -35,13 +35,20 @@ class ProviderCreateParams(TypedDict, total=False):
Must not contain HTML tags (e.g. `