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        public_api_root: str | None = None,
 95        config_api_root: str | None = None,
 96    ) -> None:
 97        """Initialize an `AgentOrganization`.
 98
 99        Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are
100        not passed explicitly. API roots default to the `AIRBYTE_CLOUD_API_URL` /
101        `AIRBYTE_CLOUD_CONFIG_API_URL` environment variables (public Airbyte Cloud when unset);
102        custom roots require `AIRBYTE_AGENTS_API_URL`. The organization ID is optional: the
103        Agents API infers it when the credentials belong to exactly one organization.
104        """
105        self._credentials = _AirbyteCredentials.from_auth(
106            organization_id=organization_id,
107            client_id=client_id,
108            client_secret=client_secret,
109            bearer_token=bearer_token,
110            public_api_root=public_api_root,
111            config_api_root=config_api_root,
112            # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env
113            # fallback, since an env bearer token plus explicit client creds is rejected
114            # as mutually exclusive auth.
115            env_vars=not (client_id or client_secret or bearer_token),
116        )
117
118        self.organization_id: str | None = self._credentials.organization_id
119        """The organization ID, when known."""
120
121    @classmethod
122    def from_env(cls, organization_id: str | None = None) -> AgentOrganization:
123        """Create an `AgentOrganization` from the `AIRBYTE_CLOUD_*` environment variables."""
124        return cls(organization_id=organization_id)
125
126    def list_workspaces(self) -> list[AgentWorkspace]:
127        """List the workspaces visible to these credentials in the Agents API."""
128        return [
129            self._workspace_from_info(info)
130            for info in (
131                AgentWorkspaceInfo.model_validate(record)
132                for record in _api_util.list_agent_workspaces(
133                    credentials=self._credentials,
134                    organization_id=self.organization_id,
135                )
136            )
137        ]
138
139    def get_workspace(
140        self,
141        id_or_name: str | None = None,
142        /,
143        *,
144        workspace_id: str | None = None,
145        name: str | None = None,
146    ) -> AgentWorkspace:
147        """Get a workspace in this organization, by ID or by name.
148
149        Pass a single positional value to look the workspace up by either its ID or its
150        name, or name the argument to be explicit.
151
152        Lookup by an explicit `workspace_id` does not call the Agents API. Every other form
153        lists the organization's workspaces and matches on ID first, then on an exact name,
154        ignoring case.
155        """
156        lookup = _resolve_workspace_lookup(
157            id_or_name,
158            workspace_id=workspace_id,
159            name=name,
160        )
161
162        if lookup.workspace_id and not lookup.name:
163            return self._workspace_from_info(AgentWorkspaceInfo(id=lookup.workspace_id))
164
165        workspaces = self.list_workspaces()
166        if lookup.workspace_id:
167            id_matches = [
168                workspace
169                for workspace in workspaces
170                if workspace.workspace_id == lookup.workspace_id
171            ]
172            if id_matches:
173                return id_matches[0]
174
175        name_lower = (lookup.name or "").lower()
176        matches = [
177            workspace
178            for workspace in workspaces
179            if workspace.name and workspace.name.lower() == name_lower
180        ]
181        if not matches:
182            raise AirbyteError(
183                message="No workspace found with the given ID or name.",
184                guidance="Use `list_workspaces()` to see the available workspaces.",
185                context={"name": lookup.name},
186            )
187        if len(matches) > 1:
188            raise AirbyteError(
189                message="Multiple workspaces matched the given name.",
190                guidance="Pass `workspace_id` instead of `name`.",
191                context={"name": lookup.name, "match_count": len(matches)},
192            )
193        return matches[0]
194
195    def as_cloud_organization(self) -> CloudOrganization:
196        """Return this organization as an `airbyte.cloud.CloudOrganization`.
197
198        Every Agents organization is also a Cloud organization, so this conversion needs no
199        API call. It requires a known organization ID, and raises `PyAirbyteInputError`
200        when the ID is unknown.
201        """
202        if not self.organization_id:
203            raise PyAirbyteInputError(
204                message="Organization ID is required to convert to a `CloudOrganization`.",
205                guidance=(
206                    "Provide `organization_id`, or set the `AIRBYTE_CLOUD_ORGANIZATION_ID` "
207                    "environment variable."
208                ),
209            )
210        return CloudOrganization(
211            organization_id=self.organization_id,
212            client_id=self._credentials.client_id,
213            client_secret=self._credentials.client_secret,
214            bearer_token=self._credentials.bearer_token,
215            public_api_root=self._credentials.public_api_root,
216            config_api_root=self._credentials.config_api_root,
217        )
218
219    @classmethod
220    def from_cloud_organization(
221        cls,
222        cloud_organization: CloudOrganization,
223    ) -> AgentOrganization:
224        """Return a Cloud organization as an `AgentOrganization`.
225
226        Whether the organization can actually execute connector actions depends on its
227        Airbyte Agents subscription, which is only knowable per workspace. Use
228        `AgentWorkspace.from_cloud_workspace()` for an authoritative eligibility check.
229
230        Raises `AirbyteAgentsUnavailableError` when the Cloud organization uses non-public Cloud
231        API roots unless `AIRBYTE_AGENTS_API_URL` is set; the Cloud API roots are carried over so
232        the token exchange stays on the same deployment.
233        """
234        credentials = cloud_organization._credentials  # noqa: SLF001  # Same-domain conversion.
235        _api_util.check_public_cloud_api_roots(credentials)
236        return cls(
237            organization_id=cloud_organization.organization_id,
238            client_id=credentials.client_id,
239            client_secret=credentials.client_secret,
240            bearer_token=credentials.bearer_token,
241            public_api_root=credentials.public_api_root,
242            config_api_root=credentials.config_api_root,
243        )
244
245    def _workspace_from_info(self, info: AgentWorkspaceInfo) -> AgentWorkspace:
246        """Build an `AgentWorkspace` from workspace info, reusing these credentials."""
247        return AgentWorkspace(
248            workspace_id=info.id,
249            organization_id=info.organization_id or self.organization_id,
250            name=info.name,
251            client_id=self._credentials.client_id,
252            client_secret=self._credentials.client_secret,
253            bearer_token=self._credentials.bearer_token,
254            public_api_root=self._credentials.public_api_root,
255            config_api_root=self._credentials.config_api_root,
256        )
class AgentOrganization:
 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        public_api_root: str | None = None,
 96        config_api_root: str | None = None,
 97    ) -> None:
 98        """Initialize an `AgentOrganization`.
 99
100        Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are
101        not passed explicitly. API roots default to the `AIRBYTE_CLOUD_API_URL` /
102        `AIRBYTE_CLOUD_CONFIG_API_URL` environment variables (public Airbyte Cloud when unset);
103        custom roots require `AIRBYTE_AGENTS_API_URL`. The organization ID is optional: the
104        Agents API infers it when the credentials belong to exactly one organization.
105        """
106        self._credentials = _AirbyteCredentials.from_auth(
107            organization_id=organization_id,
108            client_id=client_id,
109            client_secret=client_secret,
110            bearer_token=bearer_token,
111            public_api_root=public_api_root,
112            config_api_root=config_api_root,
113            # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env
114            # fallback, since an env bearer token plus explicit client creds is rejected
115            # as mutually exclusive auth.
116            env_vars=not (client_id or client_secret or bearer_token),
117        )
118
119        self.organization_id: str | None = self._credentials.organization_id
120        """The organization ID, when known."""
121
122    @classmethod
123    def from_env(cls, organization_id: str | None = None) -> AgentOrganization:
124        """Create an `AgentOrganization` from the `AIRBYTE_CLOUD_*` environment variables."""
125        return cls(organization_id=organization_id)
126
127    def list_workspaces(self) -> list[AgentWorkspace]:
128        """List the workspaces visible to these credentials in the Agents API."""
129        return [
130            self._workspace_from_info(info)
131            for info in (
132                AgentWorkspaceInfo.model_validate(record)
133                for record in _api_util.list_agent_workspaces(
134                    credentials=self._credentials,
135                    organization_id=self.organization_id,
136                )
137            )
138        ]
139
140    def get_workspace(
141        self,
142        id_or_name: str | None = None,
143        /,
144        *,
145        workspace_id: str | None = None,
146        name: str | None = None,
147    ) -> AgentWorkspace:
148        """Get a workspace in this organization, by ID or by name.
149
150        Pass a single positional value to look the workspace up by either its ID or its
151        name, or name the argument to be explicit.
152
153        Lookup by an explicit `workspace_id` does not call the Agents API. Every other form
154        lists the organization's workspaces and matches on ID first, then on an exact name,
155        ignoring case.
156        """
157        lookup = _resolve_workspace_lookup(
158            id_or_name,
159            workspace_id=workspace_id,
160            name=name,
161        )
162
163        if lookup.workspace_id and not lookup.name:
164            return self._workspace_from_info(AgentWorkspaceInfo(id=lookup.workspace_id))
165
166        workspaces = self.list_workspaces()
167        if lookup.workspace_id:
168            id_matches = [
169                workspace
170                for workspace in workspaces
171                if workspace.workspace_id == lookup.workspace_id
172            ]
173            if id_matches:
174                return id_matches[0]
175
176        name_lower = (lookup.name or "").lower()
177        matches = [
178            workspace
179            for workspace in workspaces
180            if workspace.name and workspace.name.lower() == name_lower
181        ]
182        if not matches:
183            raise AirbyteError(
184                message="No workspace found with the given ID or name.",
185                guidance="Use `list_workspaces()` to see the available workspaces.",
186                context={"name": lookup.name},
187            )
188        if len(matches) > 1:
189            raise AirbyteError(
190                message="Multiple workspaces matched the given name.",
191                guidance="Pass `workspace_id` instead of `name`.",
192                context={"name": lookup.name, "match_count": len(matches)},
193            )
194        return matches[0]
195
196    def as_cloud_organization(self) -> CloudOrganization:
197        """Return this organization as an `airbyte.cloud.CloudOrganization`.
198
199        Every Agents organization is also a Cloud organization, so this conversion needs no
200        API call. It requires a known organization ID, and raises `PyAirbyteInputError`
201        when the ID is unknown.
202        """
203        if not self.organization_id:
204            raise PyAirbyteInputError(
205                message="Organization ID is required to convert to a `CloudOrganization`.",
206                guidance=(
207                    "Provide `organization_id`, or set the `AIRBYTE_CLOUD_ORGANIZATION_ID` "
208                    "environment variable."
209                ),
210            )
211        return CloudOrganization(
212            organization_id=self.organization_id,
213            client_id=self._credentials.client_id,
214            client_secret=self._credentials.client_secret,
215            bearer_token=self._credentials.bearer_token,
216            public_api_root=self._credentials.public_api_root,
217            config_api_root=self._credentials.config_api_root,
218        )
219
220    @classmethod
221    def from_cloud_organization(
222        cls,
223        cloud_organization: CloudOrganization,
224    ) -> AgentOrganization:
225        """Return a Cloud organization as an `AgentOrganization`.
226
227        Whether the organization can actually execute connector actions depends on its
228        Airbyte Agents subscription, which is only knowable per workspace. Use
229        `AgentWorkspace.from_cloud_workspace()` for an authoritative eligibility check.
230
231        Raises `AirbyteAgentsUnavailableError` when the Cloud organization uses non-public Cloud
232        API roots unless `AIRBYTE_AGENTS_API_URL` is set; the Cloud API roots are carried over so
233        the token exchange stays on the same deployment.
234        """
235        credentials = cloud_organization._credentials  # noqa: SLF001  # Same-domain conversion.
236        _api_util.check_public_cloud_api_roots(credentials)
237        return cls(
238            organization_id=cloud_organization.organization_id,
239            client_id=credentials.client_id,
240            client_secret=credentials.client_secret,
241            bearer_token=credentials.bearer_token,
242            public_api_root=credentials.public_api_root,
243            config_api_root=credentials.config_api_root,
244        )
245
246    def _workspace_from_info(self, info: AgentWorkspaceInfo) -> AgentWorkspace:
247        """Build an `AgentWorkspace` from workspace info, reusing these credentials."""
248        return AgentWorkspace(
249            workspace_id=info.id,
250            organization_id=info.organization_id or self.organization_id,
251            name=info.name,
252            client_id=self._credentials.client_id,
253            client_secret=self._credentials.client_secret,
254            bearer_token=self._credentials.bearer_token,
255            public_api_root=self._credentials.public_api_root,
256            config_api_root=self._credentials.config_api_root,
257        )

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)
AgentOrganization( *, organization_id: str | None = None, client_id: str | airbyte.secrets.SecretString | None = None, client_secret: str | airbyte.secrets.SecretString | None = None, bearer_token: str | airbyte.secrets.SecretString | None = None, public_api_root: str | None = None, config_api_root: str | None = None)
 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        public_api_root: str | None = None,
 96        config_api_root: str | None = None,
 97    ) -> None:
 98        """Initialize an `AgentOrganization`.
 99
100        Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are
101        not passed explicitly. API roots default to the `AIRBYTE_CLOUD_API_URL` /
102        `AIRBYTE_CLOUD_CONFIG_API_URL` environment variables (public Airbyte Cloud when unset);
103        custom roots require `AIRBYTE_AGENTS_API_URL`. The organization ID is optional: the
104        Agents API infers it when the credentials belong to exactly one organization.
105        """
106        self._credentials = _AirbyteCredentials.from_auth(
107            organization_id=organization_id,
108            client_id=client_id,
109            client_secret=client_secret,
110            bearer_token=bearer_token,
111            public_api_root=public_api_root,
112            config_api_root=config_api_root,
113            # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env
114            # fallback, since an env bearer token plus explicit client creds is rejected
115            # as mutually exclusive auth.
116            env_vars=not (client_id or client_secret or bearer_token),
117        )
118
119        self.organization_id: str | None = self._credentials.organization_id
120        """The organization ID, when known."""

Initialize an AgentOrganization.

Credentials fall back to the AIRBYTE_CLOUD_* environment variables when they are not passed explicitly. API roots default to the AIRBYTE_CLOUD_API_URL / AIRBYTE_CLOUD_CONFIG_API_URL environment variables (public Airbyte Cloud when unset); custom roots require AIRBYTE_AGENTS_API_URL. The organization ID is optional: the Agents API infers it when the credentials belong to exactly one organization.

organization_id: str | None

The organization ID, when known.

@classmethod
def from_env( cls, organization_id: str | None = None) -> AgentOrganization:
122    @classmethod
123    def from_env(cls, organization_id: str | None = None) -> AgentOrganization:
124        """Create an `AgentOrganization` from the `AIRBYTE_CLOUD_*` environment variables."""
125        return cls(organization_id=organization_id)

Create an AgentOrganization from the AIRBYTE_CLOUD_* environment variables.

def list_workspaces(self) -> list[airbyte.agents.AgentWorkspace]:
127    def list_workspaces(self) -> list[AgentWorkspace]:
128        """List the workspaces visible to these credentials in the Agents API."""
129        return [
130            self._workspace_from_info(info)
131            for info in (
132                AgentWorkspaceInfo.model_validate(record)
133                for record in _api_util.list_agent_workspaces(
134                    credentials=self._credentials,
135                    organization_id=self.organization_id,
136                )
137            )
138        ]

List the workspaces visible to these credentials in the Agents API.

def get_workspace( self, id_or_name: str | None = None, /, *, workspace_id: str | None = None, name: str | None = None) -> airbyte.agents.AgentWorkspace:
140    def get_workspace(
141        self,
142        id_or_name: str | None = None,
143        /,
144        *,
145        workspace_id: str | None = None,
146        name: str | None = None,
147    ) -> AgentWorkspace:
148        """Get a workspace in this organization, by ID or by name.
149
150        Pass a single positional value to look the workspace up by either its ID or its
151        name, or name the argument to be explicit.
152
153        Lookup by an explicit `workspace_id` does not call the Agents API. Every other form
154        lists the organization's workspaces and matches on ID first, then on an exact name,
155        ignoring case.
156        """
157        lookup = _resolve_workspace_lookup(
158            id_or_name,
159            workspace_id=workspace_id,
160            name=name,
161        )
162
163        if lookup.workspace_id and not lookup.name:
164            return self._workspace_from_info(AgentWorkspaceInfo(id=lookup.workspace_id))
165
166        workspaces = self.list_workspaces()
167        if lookup.workspace_id:
168            id_matches = [
169                workspace
170                for workspace in workspaces
171                if workspace.workspace_id == lookup.workspace_id
172            ]
173            if id_matches:
174                return id_matches[0]
175
176        name_lower = (lookup.name or "").lower()
177        matches = [
178            workspace
179            for workspace in workspaces
180            if workspace.name and workspace.name.lower() == name_lower
181        ]
182        if not matches:
183            raise AirbyteError(
184                message="No workspace found with the given ID or name.",
185                guidance="Use `list_workspaces()` to see the available workspaces.",
186                context={"name": lookup.name},
187            )
188        if len(matches) > 1:
189            raise AirbyteError(
190                message="Multiple workspaces matched the given name.",
191                guidance="Pass `workspace_id` instead of `name`.",
192                context={"name": lookup.name, "match_count": len(matches)},
193            )
194        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.

def as_cloud_organization(self) -> airbyte.cloud.CloudOrganization:
196    def as_cloud_organization(self) -> CloudOrganization:
197        """Return this organization as an `airbyte.cloud.CloudOrganization`.
198
199        Every Agents organization is also a Cloud organization, so this conversion needs no
200        API call. It requires a known organization ID, and raises `PyAirbyteInputError`
201        when the ID is unknown.
202        """
203        if not self.organization_id:
204            raise PyAirbyteInputError(
205                message="Organization ID is required to convert to a `CloudOrganization`.",
206                guidance=(
207                    "Provide `organization_id`, or set the `AIRBYTE_CLOUD_ORGANIZATION_ID` "
208                    "environment variable."
209                ),
210            )
211        return CloudOrganization(
212            organization_id=self.organization_id,
213            client_id=self._credentials.client_id,
214            client_secret=self._credentials.client_secret,
215            bearer_token=self._credentials.bearer_token,
216            public_api_root=self._credentials.public_api_root,
217            config_api_root=self._credentials.config_api_root,
218        )

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.

@classmethod
def from_cloud_organization( cls, cloud_organization: airbyte.cloud.CloudOrganization) -> AgentOrganization:
220    @classmethod
221    def from_cloud_organization(
222        cls,
223        cloud_organization: CloudOrganization,
224    ) -> AgentOrganization:
225        """Return a Cloud organization as an `AgentOrganization`.
226
227        Whether the organization can actually execute connector actions depends on its
228        Airbyte Agents subscription, which is only knowable per workspace. Use
229        `AgentWorkspace.from_cloud_workspace()` for an authoritative eligibility check.
230
231        Raises `AirbyteAgentsUnavailableError` when the Cloud organization uses non-public Cloud
232        API roots unless `AIRBYTE_AGENTS_API_URL` is set; the Cloud API roots are carried over so
233        the token exchange stays on the same deployment.
234        """
235        credentials = cloud_organization._credentials  # noqa: SLF001  # Same-domain conversion.
236        _api_util.check_public_cloud_api_roots(credentials)
237        return cls(
238            organization_id=cloud_organization.organization_id,
239            client_id=credentials.client_id,
240            client_secret=credentials.client_secret,
241            bearer_token=credentials.bearer_token,
242            public_api_root=credentials.public_api_root,
243            config_api_root=credentials.config_api_root,
244        )

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 AirbyteAgentsUnavailableError when the Cloud organization uses non-public Cloud API roots unless AIRBYTE_AGENTS_API_URL is set; the Cloud API roots are carried over so the token exchange stays on the same deployment.