Source code for arize.api_keys.client

"""Client implementation for managing API keys in the Arize platform."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from arize._utils import unwrap_oneof
from arize.api_keys.types import ApiKeyType
from arize.constants.config import DEFAULT_LIST_LIMIT
from arize.pre_releases import ReleaseStage, prerelease_endpoint
from arize.utils.resolve import _find_space_id

if TYPE_CHECKING:
    import builtins
    from datetime import datetime

    from arize._generated.api_client.api_client import ApiClient
    from arize.api_keys.types import (
        ApiKeyStatus,
        ListApiKeysResponse,
        OrgBinding,
        RefreshApiKeyResponse,
        ServiceApiKeyCreated,
        UserApiKeyCreated,
        UserRoleAssignment,
    )
    from arize.config import SDKConfiguration

logger = logging.getLogger(__name__)


[docs] class ApiKeysClient: """Client for managing Arize API keys. This class is primarily intended for internal use within the SDK. Users are highly encouraged to access resource-specific functionality via :class:`arize.ArizeClient`. The API keys client is a thin wrapper around the generated REST API client, using the shared generated API client owned by :class:`arize.config.SDKConfiguration`. """ def __init__( self, *, sdk_config: SDKConfiguration, generated_client: ApiClient ) -> None: """ Args: sdk_config: Resolved SDK configuration. generated_client: Shared generated API client instance. """ # noqa: D205, D212 self._sdk_config = sdk_config from arize._generated import api_client as gen self._api = gen.APIKeysApi(generated_client) self._spaces_api = gen.SpacesApi(generated_client)
[docs] @prerelease_endpoint(key="api_keys.list", stage=ReleaseStage.BETA) def list( self, *, key_type: ApiKeyType | None = None, status: ApiKeyStatus | None = None, space: str | None = None, user_id: str | None = None, limit: int = DEFAULT_LIST_LIMIT, cursor: str | None = None, ) -> ListApiKeysResponse: """List API keys. This endpoint supports cursor-based pagination. Optionally filter by ``key_type``, ``status``, ``space``, and ``user_id``. **Service keys** (``key_type=ApiKeyType.SERVICE``): provide ``space`` to return all service keys for that space. When ``key_type`` is omitted alongside ``space``, service keys are returned implicitly. Optionally combine with ``user_id`` to filter by creator — available to any caller with space access. **User keys** (``key_type=ApiKeyType.USER``): returned by default (no ``space``). Provide ``user_id`` to view keys for a specific user — account admins only; non-admins receive a ``403``. Args: key_type: Optional key type filter (``ApiKeyType.USER`` or ``ApiKeyType.SERVICE``). status: Optional status filter (``ApiKeyStatus.ACTIVE`` or ``ApiKeyStatus.REVOKED``). Defaults to ``ApiKeyStatus.ACTIVE`` on the server side when omitted. space: Space name or ID. When provided, filters to service keys for that space. Accepts a human-readable name or a base64 identifier. user_id: Base64 identifier of the user whose keys to return. For service keys (with ``space``), filters by creator and is available to any caller with space access. For user keys (without ``space``), requires account admin role. limit: Maximum number of keys to return (1 to 100). Defaults to 50. cursor: Opaque pagination cursor from a previous response. Returns: A paginated API key list response from the Arize REST API. Raises: ApiException: If the API request fails. """ space_id = ( _find_space_id(self._spaces_api, space) if space is not None else None ) return self._api.list_api_keys( key_type=key_type, status=status, space_id=space_id, user_id=user_id, limit=limit, cursor=cursor, )
[docs] @prerelease_endpoint(key="api_keys.create", stage=ReleaseStage.BETA) def create( self, *, name: str, description: str | None = None, expires_at: datetime | None = None, ) -> UserApiKeyCreated: """Create a new user API key. Creates a user-type key that authenticates as the creating user with their full permissions. To create a space-scoped service key, use :meth:`create_service_key` instead. The returned :class:`UserApiKeyCreated` object contains the full raw key value in its ``key`` field. **This is the only time the raw key is returned.** Store it securely. Args: name: User-defined name for the API key (max 256 characters). description: Optional description (max 1000 characters). expires_at: Optional expiration timestamp. If omitted the key never expires. Must be a future timestamp. Returns: The created API key, including the one-time raw key value. Raises: ApiException: If the API request fails (e.g. invalid parameters or insufficient permissions). """ from arize._generated import api_client as gen user_body = gen.CreateUserApiKeyRequest( key_type=ApiKeyType.USER, name=name, description=description, expires_at=expires_at, ) body = gen.CreateApiKeyRequest(user_body) return unwrap_oneof( self._api.create_api_key(create_api_key_request=body) ) # type: ignore[return-value]
[docs] @prerelease_endpoint( key="api_keys.create_service_key", stage=ReleaseStage.BETA ) def create_service_key( self, *, name: str, orgs: builtins.list[OrgBinding], account_role: UserRoleAssignment | None = None, description: str | None = None, expires_at: datetime | None = None, ) -> ServiceApiKeyCreated: """Create a service-type API key with org and space bindings. Service keys are tied to a dedicated service account scoped to one or more organizations, each containing one or more spaces. All spaces must belong to the same account. When no role is specified for a space or org, the server applies the default predefined role (``MEMBER`` for spaces, ``READ_ONLY`` for orgs, ``MEMBER`` for accounts). The returned :class:`ServiceApiKeyCreated` object contains the full raw key value in its ``key`` field. **This is the only time the raw key is returned.** Store it securely. Args: name: User-defined name for the API key (max 256 characters). orgs: List of :class:`OrgBinding` objects, each specifying an optional org-level role and a list of :class:`SpaceBinding` objects within that org. At least one org with at least one space is required. account_role: Optional account-level role for the bot user. When ``None``, the server applies the default predefined ``MEMBER`` role. description: Optional description (max 1000 characters). expires_at: Optional expiration timestamp. If omitted the key never expires. Must be a future timestamp. Returns: The created API key, including the one-time raw key value. Raises: ValueError: If ``orgs`` is empty or any org has no space bindings. ApiException: If the API request fails (e.g. invalid role assignment or insufficient permissions). """ if not orgs: raise ValueError( "orgs must contain at least one entry for service keys" ) from arize._generated import api_client as gen # Pre-validate and resolve all space IDs before writing any bindings. resolved_space_ids: dict[str, str] = {} for org_binding in orgs: for space_binding in org_binding.spaces: resolved_space_ids[space_binding.space] = _find_space_id( self._spaces_api, space_binding.space ) org_bindings_gen = [] for org_binding in orgs: space_bindings_gen = [] for binding in org_binding.spaces: space_id = resolved_space_ids[binding.space] space_bindings_gen.append( gen.ServiceKeySpaceAssignment( space_id=space_id, role=binding.role, ) ) org_bindings_gen.append( gen.ServiceKeyOrgAssignment( org_id=org_binding.org_id, role=org_binding.role, spaces=space_bindings_gen, ) ) service_body = gen.CreateServiceApiKeyRequest( key_type=ApiKeyType.SERVICE, name=name, description=description, expires_at=expires_at, account_role=account_role, organizations=org_bindings_gen, ) body = gen.CreateApiKeyRequest(service_body) return unwrap_oneof( self._api.create_api_key(create_api_key_request=body) ) # type: ignore[return-value]
[docs] @prerelease_endpoint(key="api_keys.revoke", stage=ReleaseStage.BETA) def revoke(self, *, api_key_id: str) -> None: """Revoke an API key. The key's status is set to ``ApiKeyStatus.REVOKED`` and it is deactivated immediately and permanently. This operation is irreversible — the key will stop working right away. Revoking an already-revoked key is a no-op and still succeeds. Args: api_key_id: ID of the API key to revoke. Returns: None Raises: ApiException: If the API request fails (e.g. key not found or insufficient permissions). """ return self._api.revoke_api_key(api_key_id=api_key_id)
[docs] @prerelease_endpoint(key="api_keys.refresh", stage=ReleaseStage.BETA) def refresh( self, *, api_key_id: str, expires_at: datetime | None = None, grace_period_seconds: int | None = None, ) -> RefreshApiKeyResponse: """Refresh an existing API key. Atomically revokes the old key and issues a replacement with the same name, description, type, and scope. Use ``grace_period_seconds`` to keep the old key valid briefly while your services rotate to the new key. Args: api_key_id: ID of the API key to refresh. expires_at: New expiration for the replacement key. If omitted the replacement key will not expire. grace_period_seconds: Optional grace window, in seconds, during which the old key remains valid after refresh. If omitted or ``0``, the old key is invalidated immediately. Returns: The newly issued API key, including the one-time raw key value. Raises: ApiException: If the API request fails (e.g. key not found, already deleted, or insufficient permissions). """ from arize._generated import api_client as gen body = gen.RefreshApiKeyRequest( expires_at=expires_at, grace_period_seconds=grace_period_seconds, ) return self._api.refresh_api_key( api_key_id=api_key_id, refresh_api_key_request=body, )