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. `