Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.16.0"
".": "0.17.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -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
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard/keycard-api-9033c94c3389ccc8501a1403313165fc2985801e98b166f21766cc601e33bcea.yml
openapi_spec_hash: 85d79369d2b5eb95a49751607792b987
config_hash: e1aa3a147cff2ff69e1f0bf2c7d0e6bb
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## 0.17.0 (2026-07-22)

Full Changelog: [v0.16.0...v0.17.0](https://github.com/keycardai/keycard-python/compare/v0.16.0...v0.17.0)

### Features

* **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))
* **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))


### 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)
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -68,13 +79,15 @@ 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


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()
Expand Down
40 changes: 0 additions & 40 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,46 +261,6 @@ Methods:
- <code title="get /zones/{zone_id}/policies/{policy_id}/versions">client.zones.policies.versions.<a href="./src/keycardai_api/resources/zones/policies/versions.py">list</a>(policy_id, \*, zone_id, \*\*<a href="src/keycardai_api/types/zones/policies/version_list_params.py">params</a>) -> <a href="./src/keycardai_api/types/zones/policies/version_list_response.py">VersionListResponse</a></code>
- <code title="delete /zones/{zone_id}/policies/{policy_id}/versions/{version_id}">client.zones.policies.versions.<a href="./src/keycardai_api/resources/zones/policies/versions.py">archive</a>(version_id, \*, zone_id, policy_id) -> <a href="./src/keycardai_api/types/zones/policies/policy_version.py">PolicyVersion</a></code>

## 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:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion scripts/lint
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ else
fi

echo "==> Running pyright"
uv run pyright
uv run pyright -p .

echo "==> Running mypy"
uv run mypy .
Expand Down
30 changes: 30 additions & 0 deletions src/keycardai_api/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,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:
Expand Down Expand Up @@ -390,12 +405,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:
Expand Down
6 changes: 5 additions & 1 deletion src/keycardai_api/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,7 @@ def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]:


class SecurityOptions(TypedDict, total=False):
bearer_auth: bool
o_auth2: bool


Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/keycardai_api/_version.py
Original file line number Diff line number Diff line change
@@ -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
10 changes: 6 additions & 4 deletions src/keycardai_api/resources/organizations/sso_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions src/keycardai_api/resources/zones/policy_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"`.
Expand Down Expand Up @@ -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"`.
Expand Down
Loading
Loading