airbyte.agents.organizations
Airbyte Agents organizations.
⚠️ Experimental Interface
The Airbyte Agents Python interfaces are experimental. Class names, method signatures, and result models may change or be removed without notice between minor versions of PyAirbyte. Pin an exact PyAirbyte version if you depend on them.
1# Copyright (c) 2026 Airbyte, Inc., all rights reserved. 2"""Airbyte Agents organizations. 3 4> ## ⚠️ Experimental Interface 5> 6> **The Airbyte Agents Python interfaces are experimental.** Class names, method signatures, 7> and result models may change or be removed without notice between minor versions of 8> PyAirbyte. Pin an exact PyAirbyte version if you depend on them. 9""" 10 11from __future__ import annotations 12 13from typing import TYPE_CHECKING, NamedTuple 14 15from airbyte.agents import _api_util 16from airbyte.agents.models import AgentWorkspaceInfo 17from airbyte.agents.workspaces import AgentWorkspace 18from airbyte.cloud._credentials import _AirbyteCredentials 19from airbyte.cloud.organizations import CloudOrganization 20from airbyte.exceptions import AirbyteError, PyAirbyteInputError 21 22 23if TYPE_CHECKING: 24 from airbyte.secrets.base import SecretString 25 26 27class _WorkspaceLookup(NamedTuple): 28 """What to look a workspace up by, once the lookup arguments have been validated. 29 30 Both fields are set when the caller passed a positional value that could be either an 31 ID or a name, in which case an ID match takes precedence over a name match. 32 """ 33 34 workspace_id: str | None 35 name: str | None 36 37 38def _resolve_workspace_lookup( 39 id_or_name: str | None, 40 /, 41 *, 42 workspace_id: str | None, 43 name: str | None, 44) -> _WorkspaceLookup: 45 """Validate workspace lookup arguments and return what to look the workspace up by. 46 47 Exactly one of `workspace_id`, `name`, or the positional `id_or_name` is required. A 48 blank value is rejected rather than treated as an omitted argument. 49 """ 50 all_args = {"id_or_name": id_or_name, "workspace_id": workspace_id, "name": name} 51 52 blank_args = sorted( 53 key for key, value in all_args.items() if value is not None and not value.strip() 54 ) 55 if blank_args: 56 raise PyAirbyteInputError( 57 message="Workspace lookup arguments cannot be blank.", 58 guidance="Omit the argument entirely, or pass a non-blank value.", 59 context={"blank_args": blank_args}, 60 ) 61 62 provided = sorted(key for key, value in all_args.items() if value) 63 if len(provided) != 1: 64 raise PyAirbyteInputError( 65 message="Exactly one workspace lookup argument is required.", 66 guidance="Pass a workspace ID or name positionally, or as `workspace_id` or `name`.", 67 context={"provided": provided}, 68 ) 69 70 if id_or_name: 71 return _WorkspaceLookup(workspace_id=id_or_name, name=id_or_name) 72 73 return _WorkspaceLookup(workspace_id=workspace_id, name=name) 74 75 76class AgentOrganization: 77 """An organization on the Airbyte Agents platform. 78 79 ```python 80 from airbyte import agents 81 82 organization = agents.AgentOrganization.from_env() 83 workspace = organization.get_workspace("my-workspace") # by ID or name (case insensitive) 84 ``` 85 """ 86 87 def __init__( 88 self, 89 *, 90 organization_id: str | None = None, 91 client_id: str | SecretString | None = None, 92 client_secret: str | SecretString | None = None, 93 bearer_token: str | SecretString | None = None, 94 ) -> None: 95 """Initialize an `AgentOrganization`. 96 97 Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are 98 not passed explicitly. The organization ID is optional: the Agents API infers it 99 when the credentials belong to exactly one organization. 100 """ 101 self._credentials = _AirbyteCredentials.from_auth( 102 organization_id=organization_id, 103 client_id=client_id, 104 client_secret=client_secret, 105 bearer_token=bearer_token, 106 # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env 107 # fallback, since an env bearer token plus explicit client creds is rejected 108 # as mutually exclusive auth. 109 env_vars=not (client_id or client_secret or bearer_token), 110 ) 111 112 self.organization_id: str | None = self._credentials.organization_id 113 """The organization ID, when known.""" 114 115 @classmethod 116 def from_env(cls, organization_id: str | None = None) -> AgentOrganization: 117 """Create an `AgentOrganization` from the `AIRBYTE_CLOUD_*` environment variables.""" 118 return cls(organization_id=organization_id) 119 120 def list_workspaces(self) -> list[AgentWorkspace]: 121 """List the workspaces visible to these credentials in the Agents API.""" 122 return [ 123 self._workspace_from_info(info) 124 for info in ( 125 AgentWorkspaceInfo.model_validate(record) 126 for record in _api_util.list_agent_workspaces( 127 credentials=self._credentials, 128 organization_id=self.organization_id, 129 ) 130 ) 131 ] 132 133 def get_workspace( 134 self, 135 id_or_name: str | None = None, 136 /, 137 *, 138 workspace_id: str | None = None, 139 name: str | None = None, 140 ) -> AgentWorkspace: 141 """Get a workspace in this organization, by ID or by name. 142 143 Pass a single positional value to look the workspace up by either its ID or its 144 name, or name the argument to be explicit. 145 146 Lookup by an explicit `workspace_id` does not call the Agents API. Every other form 147 lists the organization's workspaces and matches on ID first, then on an exact name, 148 ignoring case. 149 """ 150 lookup = _resolve_workspace_lookup( 151 id_or_name, 152 workspace_id=workspace_id, 153 name=name, 154 ) 155 156 if lookup.workspace_id and not lookup.name: 157 return self._workspace_from_info(AgentWorkspaceInfo(id=lookup.workspace_id)) 158 159 workspaces = self.list_workspaces() 160 if lookup.workspace_id: 161 id_matches = [ 162 workspace 163 for workspace in workspaces 164 if workspace.workspace_id == lookup.workspace_id 165 ] 166 if id_matches: 167 return id_matches[0] 168 169 name_lower = (lookup.name or "").lower() 170 matches = [ 171 workspace 172 for workspace in workspaces 173 if workspace.name and workspace.name.lower() == name_lower 174 ] 175 if not matches: 176 raise AirbyteError( 177 message="No workspace found with the given ID or name.", 178 guidance="Use `list_workspaces()` to see the available workspaces.", 179 context={"name": lookup.name}, 180 ) 181 if len(matches) > 1: 182 raise AirbyteError( 183 message="Multiple workspaces matched the given name.", 184 guidance="Pass `workspace_id` instead of `name`.", 185 context={"name": lookup.name, "match_count": len(matches)}, 186 ) 187 return matches[0] 188 189 def as_cloud_organization(self) -> CloudOrganization: 190 """Return this organization as an `airbyte.cloud.CloudOrganization`. 191 192 Every Agents organization is also a Cloud organization, so this conversion needs no 193 API call. It requires a known organization ID, and raises `PyAirbyteInputError` 194 when the ID is unknown. 195 """ 196 if not self.organization_id: 197 raise PyAirbyteInputError( 198 message="Organization ID is required to convert to a `CloudOrganization`.", 199 guidance=( 200 "Provide `organization_id`, or set the `AIRBYTE_CLOUD_ORGANIZATION_ID` " 201 "environment variable." 202 ), 203 ) 204 return CloudOrganization( 205 organization_id=self.organization_id, 206 client_id=self._credentials.client_id, 207 client_secret=self._credentials.client_secret, 208 bearer_token=self._credentials.bearer_token, 209 public_api_root=self._credentials.public_api_root, 210 config_api_root=self._credentials.config_api_root, 211 ) 212 213 @classmethod 214 def from_cloud_organization( 215 cls, 216 cloud_organization: CloudOrganization, 217 ) -> AgentOrganization: 218 """Return a Cloud organization as an `AgentOrganization`. 219 220 Whether the organization can actually execute connector actions depends on its 221 Airbyte Agents subscription, which is only knowable per workspace. Use 222 `AgentWorkspace.from_cloud_workspace()` for an authoritative eligibility check. 223 224 Raises `PyAirbyteInputError` when the Cloud organization uses non-public Cloud API 225 roots, since an `AgentOrganization` cannot carry them. 226 """ 227 credentials = cloud_organization._credentials # noqa: SLF001 # Same-domain conversion. 228 _api_util.check_public_cloud_api_roots(credentials) 229 return cls( 230 organization_id=cloud_organization.organization_id, 231 client_id=credentials.client_id, 232 client_secret=credentials.client_secret, 233 bearer_token=credentials.bearer_token, 234 ) 235 236 def _workspace_from_info(self, info: AgentWorkspaceInfo) -> AgentWorkspace: 237 """Build an `AgentWorkspace` from workspace info, reusing these credentials.""" 238 return AgentWorkspace( 239 workspace_id=info.id, 240 organization_id=info.organization_id or self.organization_id, 241 name=info.name, 242 client_id=self._credentials.client_id, 243 client_secret=self._credentials.client_secret, 244 bearer_token=self._credentials.bearer_token, 245 )
77class AgentOrganization: 78 """An organization on the Airbyte Agents platform. 79 80 ```python 81 from airbyte import agents 82 83 organization = agents.AgentOrganization.from_env() 84 workspace = organization.get_workspace("my-workspace") # by ID or name (case insensitive) 85 ``` 86 """ 87 88 def __init__( 89 self, 90 *, 91 organization_id: str | None = None, 92 client_id: str | SecretString | None = None, 93 client_secret: str | SecretString | None = None, 94 bearer_token: str | SecretString | None = None, 95 ) -> None: 96 """Initialize an `AgentOrganization`. 97 98 Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are 99 not passed explicitly. The organization ID is optional: the Agents API infers it 100 when the credentials belong to exactly one organization. 101 """ 102 self._credentials = _AirbyteCredentials.from_auth( 103 organization_id=organization_id, 104 client_id=client_id, 105 client_secret=client_secret, 106 bearer_token=bearer_token, 107 # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env 108 # fallback, since an env bearer token plus explicit client creds is rejected 109 # as mutually exclusive auth. 110 env_vars=not (client_id or client_secret or bearer_token), 111 ) 112 113 self.organization_id: str | None = self._credentials.organization_id 114 """The organization ID, when known.""" 115 116 @classmethod 117 def from_env(cls, organization_id: str | None = None) -> AgentOrganization: 118 """Create an `AgentOrganization` from the `AIRBYTE_CLOUD_*` environment variables.""" 119 return cls(organization_id=organization_id) 120 121 def list_workspaces(self) -> list[AgentWorkspace]: 122 """List the workspaces visible to these credentials in the Agents API.""" 123 return [ 124 self._workspace_from_info(info) 125 for info in ( 126 AgentWorkspaceInfo.model_validate(record) 127 for record in _api_util.list_agent_workspaces( 128 credentials=self._credentials, 129 organization_id=self.organization_id, 130 ) 131 ) 132 ] 133 134 def get_workspace( 135 self, 136 id_or_name: str | None = None, 137 /, 138 *, 139 workspace_id: str | None = None, 140 name: str | None = None, 141 ) -> AgentWorkspace: 142 """Get a workspace in this organization, by ID or by name. 143 144 Pass a single positional value to look the workspace up by either its ID or its 145 name, or name the argument to be explicit. 146 147 Lookup by an explicit `workspace_id` does not call the Agents API. Every other form 148 lists the organization's workspaces and matches on ID first, then on an exact name, 149 ignoring case. 150 """ 151 lookup = _resolve_workspace_lookup( 152 id_or_name, 153 workspace_id=workspace_id, 154 name=name, 155 ) 156 157 if lookup.workspace_id and not lookup.name: 158 return self._workspace_from_info(AgentWorkspaceInfo(id=lookup.workspace_id)) 159 160 workspaces = self.list_workspaces() 161 if lookup.workspace_id: 162 id_matches = [ 163 workspace 164 for workspace in workspaces 165 if workspace.workspace_id == lookup.workspace_id 166 ] 167 if id_matches: 168 return id_matches[0] 169 170 name_lower = (lookup.name or "").lower() 171 matches = [ 172 workspace 173 for workspace in workspaces 174 if workspace.name and workspace.name.lower() == name_lower 175 ] 176 if not matches: 177 raise AirbyteError( 178 message="No workspace found with the given ID or name.", 179 guidance="Use `list_workspaces()` to see the available workspaces.", 180 context={"name": lookup.name}, 181 ) 182 if len(matches) > 1: 183 raise AirbyteError( 184 message="Multiple workspaces matched the given name.", 185 guidance="Pass `workspace_id` instead of `name`.", 186 context={"name": lookup.name, "match_count": len(matches)}, 187 ) 188 return matches[0] 189 190 def as_cloud_organization(self) -> CloudOrganization: 191 """Return this organization as an `airbyte.cloud.CloudOrganization`. 192 193 Every Agents organization is also a Cloud organization, so this conversion needs no 194 API call. It requires a known organization ID, and raises `PyAirbyteInputError` 195 when the ID is unknown. 196 """ 197 if not self.organization_id: 198 raise PyAirbyteInputError( 199 message="Organization ID is required to convert to a `CloudOrganization`.", 200 guidance=( 201 "Provide `organization_id`, or set the `AIRBYTE_CLOUD_ORGANIZATION_ID` " 202 "environment variable." 203 ), 204 ) 205 return CloudOrganization( 206 organization_id=self.organization_id, 207 client_id=self._credentials.client_id, 208 client_secret=self._credentials.client_secret, 209 bearer_token=self._credentials.bearer_token, 210 public_api_root=self._credentials.public_api_root, 211 config_api_root=self._credentials.config_api_root, 212 ) 213 214 @classmethod 215 def from_cloud_organization( 216 cls, 217 cloud_organization: CloudOrganization, 218 ) -> AgentOrganization: 219 """Return a Cloud organization as an `AgentOrganization`. 220 221 Whether the organization can actually execute connector actions depends on its 222 Airbyte Agents subscription, which is only knowable per workspace. Use 223 `AgentWorkspace.from_cloud_workspace()` for an authoritative eligibility check. 224 225 Raises `PyAirbyteInputError` when the Cloud organization uses non-public Cloud API 226 roots, since an `AgentOrganization` cannot carry them. 227 """ 228 credentials = cloud_organization._credentials # noqa: SLF001 # Same-domain conversion. 229 _api_util.check_public_cloud_api_roots(credentials) 230 return cls( 231 organization_id=cloud_organization.organization_id, 232 client_id=credentials.client_id, 233 client_secret=credentials.client_secret, 234 bearer_token=credentials.bearer_token, 235 ) 236 237 def _workspace_from_info(self, info: AgentWorkspaceInfo) -> AgentWorkspace: 238 """Build an `AgentWorkspace` from workspace info, reusing these credentials.""" 239 return AgentWorkspace( 240 workspace_id=info.id, 241 organization_id=info.organization_id or self.organization_id, 242 name=info.name, 243 client_id=self._credentials.client_id, 244 client_secret=self._credentials.client_secret, 245 bearer_token=self._credentials.bearer_token, 246 )
An organization on the Airbyte Agents platform.
from airbyte import agents
organization = agents.AgentOrganization.from_env()
workspace = organization.get_workspace("my-workspace") # by ID or name (case insensitive)
88 def __init__( 89 self, 90 *, 91 organization_id: str | None = None, 92 client_id: str | SecretString | None = None, 93 client_secret: str | SecretString | None = None, 94 bearer_token: str | SecretString | None = None, 95 ) -> None: 96 """Initialize an `AgentOrganization`. 97 98 Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are 99 not passed explicitly. The organization ID is optional: the Agents API infers it 100 when the credentials belong to exactly one organization. 101 """ 102 self._credentials = _AirbyteCredentials.from_auth( 103 organization_id=organization_id, 104 client_id=client_id, 105 client_secret=client_secret, 106 bearer_token=bearer_token, 107 # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env 108 # fallback, since an env bearer token plus explicit client creds is rejected 109 # as mutually exclusive auth. 110 env_vars=not (client_id or client_secret or bearer_token), 111 ) 112 113 self.organization_id: str | None = self._credentials.organization_id 114 """The organization ID, when known."""
Initialize an AgentOrganization.
Credentials fall back to the AIRBYTE_CLOUD_* environment variables when they are
not passed explicitly. The organization ID is optional: the Agents API infers it
when the credentials belong to exactly one organization.
116 @classmethod 117 def from_env(cls, organization_id: str | None = None) -> AgentOrganization: 118 """Create an `AgentOrganization` from the `AIRBYTE_CLOUD_*` environment variables.""" 119 return cls(organization_id=organization_id)
Create an AgentOrganization from the AIRBYTE_CLOUD_* environment variables.
121 def list_workspaces(self) -> list[AgentWorkspace]: 122 """List the workspaces visible to these credentials in the Agents API.""" 123 return [ 124 self._workspace_from_info(info) 125 for info in ( 126 AgentWorkspaceInfo.model_validate(record) 127 for record in _api_util.list_agent_workspaces( 128 credentials=self._credentials, 129 organization_id=self.organization_id, 130 ) 131 ) 132 ]
List the workspaces visible to these credentials in the Agents API.
134 def get_workspace( 135 self, 136 id_or_name: str | None = None, 137 /, 138 *, 139 workspace_id: str | None = None, 140 name: str | None = None, 141 ) -> AgentWorkspace: 142 """Get a workspace in this organization, by ID or by name. 143 144 Pass a single positional value to look the workspace up by either its ID or its 145 name, or name the argument to be explicit. 146 147 Lookup by an explicit `workspace_id` does not call the Agents API. Every other form 148 lists the organization's workspaces and matches on ID first, then on an exact name, 149 ignoring case. 150 """ 151 lookup = _resolve_workspace_lookup( 152 id_or_name, 153 workspace_id=workspace_id, 154 name=name, 155 ) 156 157 if lookup.workspace_id and not lookup.name: 158 return self._workspace_from_info(AgentWorkspaceInfo(id=lookup.workspace_id)) 159 160 workspaces = self.list_workspaces() 161 if lookup.workspace_id: 162 id_matches = [ 163 workspace 164 for workspace in workspaces 165 if workspace.workspace_id == lookup.workspace_id 166 ] 167 if id_matches: 168 return id_matches[0] 169 170 name_lower = (lookup.name or "").lower() 171 matches = [ 172 workspace 173 for workspace in workspaces 174 if workspace.name and workspace.name.lower() == name_lower 175 ] 176 if not matches: 177 raise AirbyteError( 178 message="No workspace found with the given ID or name.", 179 guidance="Use `list_workspaces()` to see the available workspaces.", 180 context={"name": lookup.name}, 181 ) 182 if len(matches) > 1: 183 raise AirbyteError( 184 message="Multiple workspaces matched the given name.", 185 guidance="Pass `workspace_id` instead of `name`.", 186 context={"name": lookup.name, "match_count": len(matches)}, 187 ) 188 return matches[0]
Get a workspace in this organization, by ID or by name.
Pass a single positional value to look the workspace up by either its ID or its name, or name the argument to be explicit.
Lookup by an explicit workspace_id does not call the Agents API. Every other form
lists the organization's workspaces and matches on ID first, then on an exact name,
ignoring case.
190 def as_cloud_organization(self) -> CloudOrganization: 191 """Return this organization as an `airbyte.cloud.CloudOrganization`. 192 193 Every Agents organization is also a Cloud organization, so this conversion needs no 194 API call. It requires a known organization ID, and raises `PyAirbyteInputError` 195 when the ID is unknown. 196 """ 197 if not self.organization_id: 198 raise PyAirbyteInputError( 199 message="Organization ID is required to convert to a `CloudOrganization`.", 200 guidance=( 201 "Provide `organization_id`, or set the `AIRBYTE_CLOUD_ORGANIZATION_ID` " 202 "environment variable." 203 ), 204 ) 205 return CloudOrganization( 206 organization_id=self.organization_id, 207 client_id=self._credentials.client_id, 208 client_secret=self._credentials.client_secret, 209 bearer_token=self._credentials.bearer_token, 210 public_api_root=self._credentials.public_api_root, 211 config_api_root=self._credentials.config_api_root, 212 )
Return this organization as an airbyte.cloud.CloudOrganization.
Every Agents organization is also a Cloud organization, so this conversion needs no
API call. It requires a known organization ID, and raises PyAirbyteInputError
when the ID is unknown.
214 @classmethod 215 def from_cloud_organization( 216 cls, 217 cloud_organization: CloudOrganization, 218 ) -> AgentOrganization: 219 """Return a Cloud organization as an `AgentOrganization`. 220 221 Whether the organization can actually execute connector actions depends on its 222 Airbyte Agents subscription, which is only knowable per workspace. Use 223 `AgentWorkspace.from_cloud_workspace()` for an authoritative eligibility check. 224 225 Raises `PyAirbyteInputError` when the Cloud organization uses non-public Cloud API 226 roots, since an `AgentOrganization` cannot carry them. 227 """ 228 credentials = cloud_organization._credentials # noqa: SLF001 # Same-domain conversion. 229 _api_util.check_public_cloud_api_roots(credentials) 230 return cls( 231 organization_id=cloud_organization.organization_id, 232 client_id=credentials.client_id, 233 client_secret=credentials.client_secret, 234 bearer_token=credentials.bearer_token, 235 )
Return a Cloud organization as an AgentOrganization.
Whether the organization can actually execute connector actions depends on its
Airbyte Agents subscription, which is only knowable per workspace. Use
AgentWorkspace.from_cloud_workspace() for an authoritative eligibility check.
Raises PyAirbyteInputError when the Cloud organization uses non-public Cloud API
roots, since an AgentOrganization cannot carry them.