airbyte.agents

PyAirbyte classes and methods for the Airbyte Agents platform.

⚠️ 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.

Airbyte Agents connectors expose read and write actions on individual entities, executed one action at a time, rather than the batch record replication that airbyte.cloud provides. This module is that interface.

Airbyte Cloud credentials authenticate against the Agents API, so the AIRBYTE_CLOUD_* variables are reused. Set AIRBYTE_AGENTS_API_URL to override the Agents API root when connecting through a proxy or local development endpoint.

Usage Examples

Read entities from a connector, paging automatically as you iterate:

from airbyte import agents

workspace = agents.AgentWorkspace.from_env()
connector = workspace.get_connector("GitHub")  # by ID or name (case insensitive)

for issue in connector.iter_entities(
    "issues",
    api_args={"repository": "airbytehq/PyAirbyte"},  # Passthrough API args
):
    print(issue["title"])

Fetch a single page instead, when the result's status and metadata are needed:

result = connector.list_entities(
    "issues",
    api_args={"repository": "airbytehq/PyAirbyte"},
    page_size=50,
)
print(result.status, result.has_next_page)
for entity in result.entities:
    print(entity["title"])

Pass result.end_cursor back as cursor to page through manually:

cursor = None
while True:
    result = connector.list_entities(
        "issues",
        api_args={"repository": "airbytehq/PyAirbyte"},
        cursor=cursor,
    )
    print(len(result.entities))
    if not result.has_next_page:
        break
    cursor = result.end_cursor

Discover what a connector supports, and what an organization can reach:

organization = agents.AgentOrganization.from_env()
for workspace in organization.list_workspaces():
    print(workspace.workspace_id, workspace.name)

print(connector.inspect().integration_name)

Convert between the Cloud and Agents domains:

from airbyte.cloud import CloudWorkspace

cloud_workspace = CloudWorkspace.from_env()
agent_workspace = agents.AgentWorkspace.from_cloud_workspace(cloud_workspace)
back_to_cloud = agent_workspace.as_cloud_workspace()
  1# Copyright (c) 2026 Airbyte, Inc., all rights reserved.
  2"""PyAirbyte classes and methods for the Airbyte Agents platform.
  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
 10Airbyte Agents connectors expose read and write actions on individual entities, executed
 11one action at a time, rather than the batch record replication that `airbyte.cloud`
 12provides. This module is that interface.
 13
 14Airbyte Cloud credentials authenticate against the Agents API, so the `AIRBYTE_CLOUD_*`
 15variables are reused. Set `AIRBYTE_AGENTS_API_URL` to override the Agents API root when
 16connecting through a proxy or local development endpoint.
 17
 18## Usage Examples
 19
 20Read entities from a connector, paging automatically as you iterate:
 21
 22```python
 23from airbyte import agents
 24
 25workspace = agents.AgentWorkspace.from_env()
 26connector = workspace.get_connector("GitHub")  # by ID or name (case insensitive)
 27
 28for issue in connector.iter_entities(
 29    "issues",
 30    api_args={"repository": "airbytehq/PyAirbyte"},  # Passthrough API args
 31):
 32    print(issue["title"])
 33```
 34
 35Fetch a single page instead, when the result's status and metadata are needed:
 36
 37```python
 38result = connector.list_entities(
 39    "issues",
 40    api_args={"repository": "airbytehq/PyAirbyte"},
 41    page_size=50,
 42)
 43print(result.status, result.has_next_page)
 44for entity in result.entities:
 45    print(entity["title"])
 46```
 47
 48Pass `result.end_cursor` back as `cursor` to page through manually:
 49
 50```python
 51cursor = None
 52while True:
 53    result = connector.list_entities(
 54        "issues",
 55        api_args={"repository": "airbytehq/PyAirbyte"},
 56        cursor=cursor,
 57    )
 58    print(len(result.entities))
 59    if not result.has_next_page:
 60        break
 61    cursor = result.end_cursor
 62```
 63
 64Discover what a connector supports, and what an organization can reach:
 65
 66```python
 67organization = agents.AgentOrganization.from_env()
 68for workspace in organization.list_workspaces():
 69    print(workspace.workspace_id, workspace.name)
 70
 71print(connector.inspect().integration_name)
 72```
 73
 74Convert between the Cloud and Agents domains:
 75
 76```python
 77from airbyte.cloud import CloudWorkspace
 78
 79cloud_workspace = CloudWorkspace.from_env()
 80agent_workspace = agents.AgentWorkspace.from_cloud_workspace(cloud_workspace)
 81back_to_cloud = agent_workspace.as_cloud_workspace()
 82```
 83"""
 84
 85from __future__ import annotations
 86
 87from typing import TYPE_CHECKING
 88
 89from airbyte.agents.connectors import AgentConnector
 90from airbyte.agents.models import (
 91    AgentConnectorDetails,
 92    AgentConnectorInfo,
 93    AgentConnectorMetadata,
 94    AgentContextStoreEntity,
 95    AgentContextStoreReadiness,
 96    AgentExecuteResult,
 97    AgentExecutionMetadata,
 98    AgentSkillDocs,
 99    AgentSkillInfo,
100    AgentSkillList,
101    AgentSkillSection,
102    AgentWorkspaceInfo,
103)
104from airbyte.agents.organizations import AgentOrganization
105from airbyte.agents.skills import AgentSkill
106from airbyte.agents.workspaces import AgentWorkspace
107
108
109# Submodules imported here for documentation reasons: https://github.com/mitmproxy/pdoc/issues/757
110if TYPE_CHECKING:
111    # ruff: noqa: TC004
112    from airbyte.agents import (
113        connectors,
114        models,
115        organizations,
116        skills,
117        workspaces,
118    )
119
120
121__all__ = [
122    # Submodules
123    "connectors",
124    "models",
125    "organizations",
126    "skills",
127    "workspaces",
128    # Classes
129    "AgentConnector",
130    "AgentConnectorDetails",
131    "AgentConnectorInfo",
132    "AgentConnectorMetadata",
133    "AgentContextStoreEntity",
134    "AgentContextStoreReadiness",
135    "AgentExecuteResult",
136    "AgentExecutionMetadata",
137    "AgentOrganization",
138    "AgentSkill",
139    "AgentSkillDocs",
140    "AgentSkillInfo",
141    "AgentSkillList",
142    "AgentSkillSection",
143    "AgentWorkspace",
144    "AgentWorkspaceInfo",
145]
class AgentConnector:
152class AgentConnector:
153    """A connector in an Airbyte Agents workspace.
154
155    Get one from `AgentWorkspace.get_connector()` rather than constructing it directly.
156
157    ```python
158    from airbyte import agents
159
160    workspace = agents.AgentWorkspace.from_env()
161    connector = workspace.get_connector("GitHub")  # by ID or name (case insensitive)
162    result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"})
163    for entity in result.entities:
164        print(entity["title"])
165    ```
166    """
167
168    def __init__(
169        self,
170        connector_id: str,
171        *,
172        credentials: _AirbyteCredentials,
173        name: str | None = None,
174        workspace_id: str | None = None,
175    ) -> None:
176        """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`."""
177        self.connector_id = connector_id
178        """The connector ID."""
179
180        self.workspace_id = workspace_id
181        """The workspace ID."""
182
183        self._credentials = credentials
184        self._name = name
185        self._details: AgentConnectorDetails | None = None
186
187    @property
188    def name(self) -> str | None:
189        """The connector name, fetched from the Agents API if not already known."""
190        if self._name is None:
191            self._name = self.inspect().name
192        return self._name
193
194    def inspect(self, *, force_refresh: bool = False) -> AgentConnectorDetails:
195        """Return connector metadata from the Agents API `inspect` endpoint.
196
197        The result is cached; pass `force_refresh=True` to fetch it again.
198        """
199        if self._details is None or force_refresh:
200            self._details = AgentConnectorDetails.model_validate(
201                _api_util.inspect_agent_connector(
202                    connector_id=self.connector_id,
203                    credentials=self._credentials,
204                    organization_id=self._credentials.organization_id,
205                )
206            )
207            self._name = self._details.name or self._name
208        return self._details
209
210    def execute(  # noqa: PLR0913  # Explicit args are the point of this public API.
211        self,
212        entity_type: str,
213        action: AgentAction | str,
214        api_args: dict[str, Any] | None = None,
215        *,
216        select_fields: list[str] | None = None,
217        exclude_fields: list[str] | None = None,
218        page_size: int | None = None,
219        cursor: str | None = None,
220        workspace_id: str | None = None,
221        skip_truncation: bool = True,
222        intent: str | None = None,
223    ) -> AgentExecuteResult:
224        """Execute a single action against one entity type on this connector.
225
226        `entity_type` and `action` are connector-specific, for example `issues` and `list`.
227        Use `inspect()` to see what a connector supports.
228
229        `api_args` holds connector-specific arguments passed through to the connector, for
230        example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted
231        by PyAirbyte or by the Agents API itself:
232
233        - `select_fields` and `exclude_fields` prune fields from returned entities.
234        - `page_size` and `cursor` are merged into `api_args` as pagination arguments. Context
235          Store `search` uses `limit` and `cursor`, `sql_select` uses the top-level `cursor`, and
236          direct connector actions use their own pagination arguments in `api_args`. Pass the
237          result cursor according to the action type.
238        - `workspace_id` selects the workspace an action runs against. It defaults to the
239          connector's workspace and is currently sent for `sql_select`, the only action the
240          Agents API scopes by workspace; direct connector actions are scoped by the connector.
241        - `skip_truncation` disables the Agents API's default truncation of large payloads.
242        - `intent` is a free-text description of why the action is being run, which some
243          connectors use to refine results.
244
245        The `download` action is rejected, because it returns a binary stream and PyAirbyte
246        does not yet support streaming responses.
247        """
248        if action in UNSUPPORTED_ACTIONS:
249            raise PyAirbyteInputError(
250                message=f"The {action!r} action is not supported by PyAirbyte.",
251                guidance=(
252                    "This action returns a binary stream instead of JSON, and PyAirbyte does "
253                    "not yet support streaming responses."
254                ),
255                context={"entity_type": entity_type, "action": action},
256            )
257
258        if action not in {*AgentReadAction, *AgentWriteAction}:
259            action_names = ", ".join(
260                member.value for member in (*AgentReadAction, *AgentWriteAction)
261            )
262            raise PyAirbyteInputError(
263                message=f"The {action!r} action is not a valid action name for `execute`.",
264                guidance=f"Use one of: {action_names}.",
265                context={"entity_type": entity_type, "action": action},
266            )
267
268        action_value = action.value if isinstance(action, Enum) else action
269        params = _build_params(api_args=api_args, page_size=page_size, cursor=cursor)
270        if action_value == AgentReadAction.SQL_SELECT.value:
271            resolved_workspace_id = workspace_id or self.workspace_id
272            if (
273                resolved_workspace_id is not None
274                and params.get("workspace_id") is None
275                and params.get("workspace_name") is None
276            ):
277                params.pop("workspace_name", None)
278                params["workspace_id"] = resolved_workspace_id
279        request_body: dict[str, Any] = {
280            "entity": entity_type,
281            "action": action_value,
282            "params": params,
283            "skip_truncation": skip_truncation,
284        }
285        if select_fields is not None:
286            request_body["select_fields"] = select_fields
287        if exclude_fields is not None:
288            request_body["exclude_fields"] = exclude_fields
289        if intent is not None:
290            request_body["intent"] = intent
291
292        return AgentExecuteResult.model_validate(
293            _api_util.execute_agent_connector_action(
294                connector_id=self.connector_id,
295                request_body=request_body,
296                credentials=self._credentials,
297                organization_id=self._credentials.organization_id,
298            )
299        )
300
301    def list_entities(
302        self,
303        entity_type: str,
304        api_args: dict[str, Any] | None = None,
305        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
306    ) -> AgentExecuteResult:
307        """Run the `list` action, which returns a page of entities of `entity_type`."""
308        return self.execute(entity_type, "list", api_args, **kwargs)
309
310    def iter_entities(
311        self,
312        entity_type: str,
313        api_args: dict[str, Any] | None = None,
314        *,
315        limit: int | None = None,
316        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `list_entities()`.
317    ) -> Iterator[dict[str, Any]]:
318        """Yield entities of `entity_type`, following the connector's pagination cursor.
319
320        This is the pagination-free way to read entities: each page is fetched lazily as
321        the caller iterates, so no cursor bookkeeping is needed.
322
323        ```python
324        for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
325            print(issue["title"])
326        ```
327
328        `limit` caps how many entities are yielded in total, which matters for entity types
329        with no natural end. Pass `page_size` to control how many are fetched per request.
330
331        Iteration stops early if the connector reports another page without advancing its
332        cursor, rather than requesting the same page forever.
333
334        Use `list_entities()` instead when a single page is enough, or when the result's
335        `status`, `warning`, or `execution_metadata` are needed.
336        """
337        cursor: str | None = kwargs.pop("cursor", None)
338        seen_cursors: set[str] = set()
339        yielded = 0
340
341        while True:
342            result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs)
343            for agent_entity in result.entities:
344                yield agent_entity
345                yielded += 1
346                if limit is not None and yielded >= limit:
347                    return
348
349            cursor = result.end_cursor
350            if not result.has_next_page or cursor is None or cursor in seen_cursors:
351                return
352            seen_cursors.add(cursor)
353
354    def search_entities(
355        self,
356        entity_type: str,
357        api_args: dict[str, Any] | None = None,
358        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
359    ) -> AgentExecuteResult:
360        """Run the `search` action, which returns matching entities of `entity_type`."""
361        return self.execute(entity_type, "search", api_args, **kwargs)
362
363    def get_entity(
364        self,
365        entity_type: str,
366        api_args: dict[str, Any] | None = None,
367        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
368    ) -> AgentExecuteResult:
369        """Run the `get` action, which returns a single entity of `entity_type`."""
370        return self.execute(entity_type, "get", api_args, **kwargs)
371
372    def create_entity(
373        self,
374        entity_type: str,
375        api_args: dict[str, Any] | None = None,
376        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
377    ) -> AgentExecuteResult:
378        """Run the `create` action, which creates an entity of `entity_type`."""
379        return self.execute(entity_type, "create", api_args, **kwargs)
380
381    def update_entity(
382        self,
383        entity_type: str,
384        api_args: dict[str, Any] | None = None,
385        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
386    ) -> AgentExecuteResult:
387        """Run the `update` action, which updates an entity of `entity_type`."""
388        return self.execute(entity_type, "update", api_args, **kwargs)
389
390    def delete_entity(
391        self,
392        entity_type: str,
393        api_args: dict[str, Any] | None = None,
394        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
395    ) -> AgentExecuteResult:
396        """Run the `delete` action, which deletes an entity of `entity_type`."""
397        return self.execute(entity_type, "delete", api_args, **kwargs)

A connector in an Airbyte Agents workspace.

Get one from AgentWorkspace.get_connector() rather than constructing it directly.

from airbyte import agents

workspace = agents.AgentWorkspace.from_env()
connector = workspace.get_connector("GitHub")  # by ID or name (case insensitive)
result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"})
for entity in result.entities:
    print(entity["title"])
AgentConnector( connector_id: str, *, credentials: airbyte.cloud._credentials._AirbyteCredentials, name: str | None = None, workspace_id: str | None = None)
168    def __init__(
169        self,
170        connector_id: str,
171        *,
172        credentials: _AirbyteCredentials,
173        name: str | None = None,
174        workspace_id: str | None = None,
175    ) -> None:
176        """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`."""
177        self.connector_id = connector_id
178        """The connector ID."""
179
180        self.workspace_id = workspace_id
181        """The workspace ID."""
182
183        self._credentials = credentials
184        self._name = name
185        self._details: AgentConnectorDetails | None = None
connector_id

The connector ID.

workspace_id

The workspace ID.

name: str | None
187    @property
188    def name(self) -> str | None:
189        """The connector name, fetched from the Agents API if not already known."""
190        if self._name is None:
191            self._name = self.inspect().name
192        return self._name

The connector name, fetched from the Agents API if not already known.

def inspect( self, *, force_refresh: bool = False) -> AgentConnectorDetails:
194    def inspect(self, *, force_refresh: bool = False) -> AgentConnectorDetails:
195        """Return connector metadata from the Agents API `inspect` endpoint.
196
197        The result is cached; pass `force_refresh=True` to fetch it again.
198        """
199        if self._details is None or force_refresh:
200            self._details = AgentConnectorDetails.model_validate(
201                _api_util.inspect_agent_connector(
202                    connector_id=self.connector_id,
203                    credentials=self._credentials,
204                    organization_id=self._credentials.organization_id,
205                )
206            )
207            self._name = self._details.name or self._name
208        return self._details

Return connector metadata from the Agents API inspect endpoint.

The result is cached; pass force_refresh=True to fetch it again.

def execute( self, entity_type: str, action: airbyte.agents.connectors.AgentReadAction | airbyte.agents.connectors.AgentWriteAction | str, api_args: dict[str, typing.Any] | None = None, *, select_fields: list[str] | None = None, exclude_fields: list[str] | None = None, page_size: int | None = None, cursor: str | None = None, workspace_id: str | None = None, skip_truncation: bool = True, intent: str | None = None) -> AgentExecuteResult:
210    def execute(  # noqa: PLR0913  # Explicit args are the point of this public API.
211        self,
212        entity_type: str,
213        action: AgentAction | str,
214        api_args: dict[str, Any] | None = None,
215        *,
216        select_fields: list[str] | None = None,
217        exclude_fields: list[str] | None = None,
218        page_size: int | None = None,
219        cursor: str | None = None,
220        workspace_id: str | None = None,
221        skip_truncation: bool = True,
222        intent: str | None = None,
223    ) -> AgentExecuteResult:
224        """Execute a single action against one entity type on this connector.
225
226        `entity_type` and `action` are connector-specific, for example `issues` and `list`.
227        Use `inspect()` to see what a connector supports.
228
229        `api_args` holds connector-specific arguments passed through to the connector, for
230        example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted
231        by PyAirbyte or by the Agents API itself:
232
233        - `select_fields` and `exclude_fields` prune fields from returned entities.
234        - `page_size` and `cursor` are merged into `api_args` as pagination arguments. Context
235          Store `search` uses `limit` and `cursor`, `sql_select` uses the top-level `cursor`, and
236          direct connector actions use their own pagination arguments in `api_args`. Pass the
237          result cursor according to the action type.
238        - `workspace_id` selects the workspace an action runs against. It defaults to the
239          connector's workspace and is currently sent for `sql_select`, the only action the
240          Agents API scopes by workspace; direct connector actions are scoped by the connector.
241        - `skip_truncation` disables the Agents API's default truncation of large payloads.
242        - `intent` is a free-text description of why the action is being run, which some
243          connectors use to refine results.
244
245        The `download` action is rejected, because it returns a binary stream and PyAirbyte
246        does not yet support streaming responses.
247        """
248        if action in UNSUPPORTED_ACTIONS:
249            raise PyAirbyteInputError(
250                message=f"The {action!r} action is not supported by PyAirbyte.",
251                guidance=(
252                    "This action returns a binary stream instead of JSON, and PyAirbyte does "
253                    "not yet support streaming responses."
254                ),
255                context={"entity_type": entity_type, "action": action},
256            )
257
258        if action not in {*AgentReadAction, *AgentWriteAction}:
259            action_names = ", ".join(
260                member.value for member in (*AgentReadAction, *AgentWriteAction)
261            )
262            raise PyAirbyteInputError(
263                message=f"The {action!r} action is not a valid action name for `execute`.",
264                guidance=f"Use one of: {action_names}.",
265                context={"entity_type": entity_type, "action": action},
266            )
267
268        action_value = action.value if isinstance(action, Enum) else action
269        params = _build_params(api_args=api_args, page_size=page_size, cursor=cursor)
270        if action_value == AgentReadAction.SQL_SELECT.value:
271            resolved_workspace_id = workspace_id or self.workspace_id
272            if (
273                resolved_workspace_id is not None
274                and params.get("workspace_id") is None
275                and params.get("workspace_name") is None
276            ):
277                params.pop("workspace_name", None)
278                params["workspace_id"] = resolved_workspace_id
279        request_body: dict[str, Any] = {
280            "entity": entity_type,
281            "action": action_value,
282            "params": params,
283            "skip_truncation": skip_truncation,
284        }
285        if select_fields is not None:
286            request_body["select_fields"] = select_fields
287        if exclude_fields is not None:
288            request_body["exclude_fields"] = exclude_fields
289        if intent is not None:
290            request_body["intent"] = intent
291
292        return AgentExecuteResult.model_validate(
293            _api_util.execute_agent_connector_action(
294                connector_id=self.connector_id,
295                request_body=request_body,
296                credentials=self._credentials,
297                organization_id=self._credentials.organization_id,
298            )
299        )

Execute a single action against one entity type on this connector.

entity_type and action are connector-specific, for example issues and list. Use inspect() to see what a connector supports.

api_args holds connector-specific arguments passed through to the connector, for example {"repository": "airbytehq/PyAirbyte"}. All other arguments are interpreted by PyAirbyte or by the Agents API itself:

  • select_fields and exclude_fields prune fields from returned entities.
  • page_size and cursor are merged into api_args as pagination arguments. Context Store search uses limit and cursor, sql_select uses the top-level cursor, and direct connector actions use their own pagination arguments in api_args. Pass the result cursor according to the action type.
  • workspace_id selects the workspace an action runs against. It defaults to the connector's workspace and is currently sent for sql_select, the only action the Agents API scopes by workspace; direct connector actions are scoped by the connector.
  • skip_truncation disables the Agents API's default truncation of large payloads.
  • intent is a free-text description of why the action is being run, which some connectors use to refine results.

The download action is rejected, because it returns a binary stream and PyAirbyte does not yet support streaming responses.

def list_entities( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> AgentExecuteResult:
301    def list_entities(
302        self,
303        entity_type: str,
304        api_args: dict[str, Any] | None = None,
305        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
306    ) -> AgentExecuteResult:
307        """Run the `list` action, which returns a page of entities of `entity_type`."""
308        return self.execute(entity_type, "list", api_args, **kwargs)

Run the list action, which returns a page of entities of entity_type.

def iter_entities( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, *, limit: int | None = None, **kwargs: Any) -> Iterator[dict[str, typing.Any]]:
310    def iter_entities(
311        self,
312        entity_type: str,
313        api_args: dict[str, Any] | None = None,
314        *,
315        limit: int | None = None,
316        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `list_entities()`.
317    ) -> Iterator[dict[str, Any]]:
318        """Yield entities of `entity_type`, following the connector's pagination cursor.
319
320        This is the pagination-free way to read entities: each page is fetched lazily as
321        the caller iterates, so no cursor bookkeeping is needed.
322
323        ```python
324        for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
325            print(issue["title"])
326        ```
327
328        `limit` caps how many entities are yielded in total, which matters for entity types
329        with no natural end. Pass `page_size` to control how many are fetched per request.
330
331        Iteration stops early if the connector reports another page without advancing its
332        cursor, rather than requesting the same page forever.
333
334        Use `list_entities()` instead when a single page is enough, or when the result's
335        `status`, `warning`, or `execution_metadata` are needed.
336        """
337        cursor: str | None = kwargs.pop("cursor", None)
338        seen_cursors: set[str] = set()
339        yielded = 0
340
341        while True:
342            result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs)
343            for agent_entity in result.entities:
344                yield agent_entity
345                yielded += 1
346                if limit is not None and yielded >= limit:
347                    return
348
349            cursor = result.end_cursor
350            if not result.has_next_page or cursor is None or cursor in seen_cursors:
351                return
352            seen_cursors.add(cursor)

Yield entities of entity_type, following the connector's pagination cursor.

This is the pagination-free way to read entities: each page is fetched lazily as the caller iterates, so no cursor bookkeeping is needed.

for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
    print(issue["title"])

limit caps how many entities are yielded in total, which matters for entity types with no natural end. Pass page_size to control how many are fetched per request.

Iteration stops early if the connector reports another page without advancing its cursor, rather than requesting the same page forever.

Use list_entities() instead when a single page is enough, or when the result's status, warning, or execution_metadata are needed.

def search_entities( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> AgentExecuteResult:
354    def search_entities(
355        self,
356        entity_type: str,
357        api_args: dict[str, Any] | None = None,
358        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
359    ) -> AgentExecuteResult:
360        """Run the `search` action, which returns matching entities of `entity_type`."""
361        return self.execute(entity_type, "search", api_args, **kwargs)

Run the search action, which returns matching entities of entity_type.

def get_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> AgentExecuteResult:
363    def get_entity(
364        self,
365        entity_type: str,
366        api_args: dict[str, Any] | None = None,
367        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
368    ) -> AgentExecuteResult:
369        """Run the `get` action, which returns a single entity of `entity_type`."""
370        return self.execute(entity_type, "get", api_args, **kwargs)

Run the get action, which returns a single entity of entity_type.

def create_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> AgentExecuteResult:
372    def create_entity(
373        self,
374        entity_type: str,
375        api_args: dict[str, Any] | None = None,
376        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
377    ) -> AgentExecuteResult:
378        """Run the `create` action, which creates an entity of `entity_type`."""
379        return self.execute(entity_type, "create", api_args, **kwargs)

Run the create action, which creates an entity of entity_type.

def update_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> AgentExecuteResult:
381    def update_entity(
382        self,
383        entity_type: str,
384        api_args: dict[str, Any] | None = None,
385        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
386    ) -> AgentExecuteResult:
387        """Run the `update` action, which updates an entity of `entity_type`."""
388        return self.execute(entity_type, "update", api_args, **kwargs)

Run the update action, which updates an entity of entity_type.

def delete_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> AgentExecuteResult:
390    def delete_entity(
391        self,
392        entity_type: str,
393        api_args: dict[str, Any] | None = None,
394        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
395    ) -> AgentExecuteResult:
396        """Run the `delete` action, which deletes an entity of `entity_type`."""
397        return self.execute(entity_type, "delete", api_args, **kwargs)

Run the delete action, which deletes an entity of entity_type.

class AgentConnectorDetails(pydantic.main.BaseModel):
150class AgentConnectorDetails(BaseModel):
151    """Connector metadata returned by the Agents API `inspect` endpoint."""
152
153    model_config = ConfigDict(extra="allow", populate_by_name=True)
154
155    connector_id: str
156    """The connector ID."""
157
158    name: str | None = None
159    """The connector name."""
160
161    workspace_id: str | None = None
162    """The ID of the workspace the connector belongs to."""
163
164    organization_id: str | None = None
165    """The ID of the organization the connector belongs to."""
166
167    source_definition_id: str | None = None
168    """The ID of the underlying Airbyte source definition."""
169
170    integration_name: str | None = Field(default=None, alias="source_definition_name")
171    """Name of the underlying integration, for example `GitHub` or `Snowflake`."""
172
173    docs_skill_id: str | None = None
174    """Skill ID to pass to `AgentWorkspace.get_skill(...).read_docs()` (MCP:
175    `read_agent_skill_docs`) for this connector's usage docs."""
176
177    context_store_readiness: AgentContextStoreReadiness | None = None
178    """Context Store readiness information, when reported."""
179
180    warnings: list[Any] = Field(default_factory=list)
181    """Warnings reported by the Agents API, for example degraded capabilities."""
182
183    @property
184    def context_store_entities(self) -> list[str]:
185        """The entity names this connector can cache in the Context Store.
186
187        Note that this lists Context Store-supported entities specifically. The Agents API
188        does not publish an exhaustive list of executable entity and action pairs, so an
189        entity may be executable via `AgentConnector.execute()` without appearing here.
190        """
191        if self.context_store_readiness is None:
192            return []
193        return [
194            entity.entity
195            for entity in self.context_store_readiness.supported_context_store_entities
196        ]

Connector metadata returned by the Agents API inspect endpoint.

connector_id: str = PydanticUndefined

The connector ID.

name: str | None = None

The connector name.

workspace_id: str | None = None

The ID of the workspace the connector belongs to.

organization_id: str | None = None

The ID of the organization the connector belongs to.

source_definition_id: str | None = None

The ID of the underlying Airbyte source definition.

integration_name: str | None = None

Name of the underlying integration, for example GitHub or Snowflake.

docs_skill_id: str | None = None

Skill ID to pass to AgentWorkspace.get_skill(...).read_docs() (MCP: read_agent_skill_docs) for this connector's usage docs.

context_store_readiness: AgentContextStoreReadiness | None = None

Context Store readiness information, when reported.

warnings: list[typing.Any] = PydanticUndefined

Warnings reported by the Agents API, for example degraded capabilities.

context_store_entities: list[str]
183    @property
184    def context_store_entities(self) -> list[str]:
185        """The entity names this connector can cache in the Context Store.
186
187        Note that this lists Context Store-supported entities specifically. The Agents API
188        does not publish an exhaustive list of executable entity and action pairs, so an
189        entity may be executable via `AgentConnector.execute()` without appearing here.
190        """
191        if self.context_store_readiness is None:
192            return []
193        return [
194            entity.entity
195            for entity in self.context_store_readiness.supported_context_store_entities
196        ]

The entity names this connector can cache in the Context Store.

Note that this lists Context Store-supported entities specifically. The Agents API does not publish an exhaustive list of executable entity and action pairs, so an entity may be executable via AgentConnector.execute() without appearing here.

class AgentConnectorInfo(pydantic.main.BaseModel):
42class AgentConnectorInfo(BaseModel):
43    """Summary information about a connector, as returned by the Agents API."""
44
45    model_config = ConfigDict(extra="allow")
46
47    id: str
48    """The connector ID."""
49
50    name: str | None = None
51    """The connector name, for example `GitHub - <workspace_id>`."""

Summary information about a connector, as returned by the Agents API.

id: str = PydanticUndefined

The connector ID.

name: str | None = None

The connector name, for example GitHub - <workspace_id>.

class AgentConnectorMetadata(pydantic.main.BaseModel):
211class AgentConnectorMetadata(BaseModel):
212    """Connector-reported metadata about a single action's result, including pagination."""
213
214    model_config = ConfigDict(extra="allow")
215
216    has_next_page: bool | None = None
217    """Whether more entities are available after this page, when the connector reports it."""
218
219    end_cursor: str | None = None
220    """The cursor for the next page, when one is available. Pass it as `cursor` for Context Store
221    `search`, or as the connector's own cursor argument in `api_args` for direct connector
222    actions."""

Connector-reported metadata about a single action's result, including pagination.

has_next_page: bool | None = None

Whether more entities are available after this page, when the connector reports it.

end_cursor: str | None = None

The cursor for the next page, when one is available. Pass it as cursor for Context Store search, or as the connector's own cursor argument in api_args for direct connector actions.

class AgentContextStoreEntity(pydantic.main.BaseModel):
54class AgentContextStoreEntity(BaseModel):
55    """An entity that a connector supports caching in the Airbyte Context Store."""
56
57    model_config = ConfigDict(extra="allow")
58
59    entity: str
60    """The entity name, for example `issues`."""
61
62    suggested: bool | None = None
63    """Whether Airbyte suggests caching this entity."""

An entity that a connector supports caching in the Airbyte Context Store.

entity: str = PydanticUndefined

The entity name, for example issues.

suggested: bool | None = None

Whether Airbyte suggests caching this entity.

class AgentContextStoreReadiness(pydantic.main.BaseModel):
66class AgentContextStoreReadiness(BaseModel):
67    """Context Store readiness information for a connector."""
68
69    model_config = ConfigDict(extra="allow")
70
71    supported_context_store_entities: list[AgentContextStoreEntity] = Field(default_factory=list)
72    """The entities this connector can cache in the Context Store."""
73
74    configured_cache_entities: list[dict[str, Any]] = Field(default_factory=list)
75    """The entities currently configured for caching, with their sync status."""

Context Store readiness information for a connector.

supported_context_store_entities: list[AgentContextStoreEntity] = PydanticUndefined

The entities this connector can cache in the Context Store.

configured_cache_entities: list[dict[str, typing.Any]] = PydanticUndefined

The entities currently configured for caching, with their sync status.

class AgentExecuteResult(pydantic.main.BaseModel):
225class AgentExecuteResult(BaseModel):
226    """The result of executing a single action against an Airbyte Agents connector."""
227
228    model_config = ConfigDict(extra="allow")
229
230    status: str
231    """The execution status reported by the Agents API, for example `success`."""
232
233    result: Any = None
234    """The action's payload. Entity-returning actions put a list of entities here."""
235
236    connector_metadata: AgentConnectorMetadata = Field(default_factory=AgentConnectorMetadata)
237    """Connector-reported metadata about the result, including pagination cursors."""
238
239    execution_metadata: AgentExecutionMetadata = Field(default_factory=AgentExecutionMetadata)
240    """Metadata describing how the action was executed."""
241
242    warning: dict[str, Any] | None = None
243    """A warning reported alongside an otherwise successful result."""
244
245    @field_validator("connector_metadata", "execution_metadata", mode="before")
246    @classmethod
247    def _none_to_empty(cls, value: object) -> object:
248        return {} if value is None else value
249
250    @property
251    def entities(self) -> list[dict[str, Any]]:
252        """The result as a list of entities.
253
254        Raises `PyAirbyteInputError` if the action did not return a list of entities. Use
255        `result` for actions whose payload is not a list of entities.
256        """
257        if not isinstance(self.result, list):
258            raise PyAirbyteInputError(
259                message="This action did not return a list of entities.",
260                guidance="Use the `result` attribute to read non-entity result payloads.",
261                context={"result_type": type(self.result).__name__},
262            )
263
264        invalid_types = sorted(
265            {type(entity).__name__ for entity in self.result if not isinstance(entity, dict)}
266        )
267        if invalid_types:
268            raise PyAirbyteInputError(
269                message="This action returned a list that is not a list of entities.",
270                guidance="Use the `result` attribute to read non-entity result payloads.",
271                context={"unexpected_item_types": invalid_types},
272            )
273        return self.result
274
275    @property
276    def has_next_page(self) -> bool:
277        """Whether the connector reported more entities after this page."""
278        return bool(self.connector_metadata.has_next_page)
279
280    @property
281    def end_cursor(self) -> str | None:
282        """The cursor for the next page, or `None` when there is no next page."""
283        return self.connector_metadata.end_cursor

The result of executing a single action against an Airbyte Agents connector.

status: str = PydanticUndefined

The execution status reported by the Agents API, for example success.

result: Any = None

The action's payload. Entity-returning actions put a list of entities here.

connector_metadata: AgentConnectorMetadata = PydanticUndefined

Connector-reported metadata about the result, including pagination cursors.

execution_metadata: AgentExecutionMetadata = PydanticUndefined

Metadata describing how the action was executed.

warning: dict[str, typing.Any] | None = None

A warning reported alongside an otherwise successful result.

entities: list[dict[str, typing.Any]]
250    @property
251    def entities(self) -> list[dict[str, Any]]:
252        """The result as a list of entities.
253
254        Raises `PyAirbyteInputError` if the action did not return a list of entities. Use
255        `result` for actions whose payload is not a list of entities.
256        """
257        if not isinstance(self.result, list):
258            raise PyAirbyteInputError(
259                message="This action did not return a list of entities.",
260                guidance="Use the `result` attribute to read non-entity result payloads.",
261                context={"result_type": type(self.result).__name__},
262            )
263
264        invalid_types = sorted(
265            {type(entity).__name__ for entity in self.result if not isinstance(entity, dict)}
266        )
267        if invalid_types:
268            raise PyAirbyteInputError(
269                message="This action returned a list that is not a list of entities.",
270                guidance="Use the `result` attribute to read non-entity result payloads.",
271                context={"unexpected_item_types": invalid_types},
272            )
273        return self.result

The result as a list of entities.

Raises PyAirbyteInputError if the action did not return a list of entities. Use result for actions whose payload is not a list of entities.

has_next_page: bool
275    @property
276    def has_next_page(self) -> bool:
277        """Whether the connector reported more entities after this page."""
278        return bool(self.connector_metadata.has_next_page)

Whether the connector reported more entities after this page.

end_cursor: str | None
280    @property
281    def end_cursor(self) -> str | None:
282        """The cursor for the next page, or `None` when there is no next page."""
283        return self.connector_metadata.end_cursor

The cursor for the next page, or None when there is no next page.

class AgentExecutionMetadata(pydantic.main.BaseModel):
199class AgentExecutionMetadata(BaseModel):
200    """Metadata describing how an Agents connector action was executed."""
201
202    model_config = ConfigDict(extra="allow")
203
204    connector_instance_id: str | None = None
205    """The connector instance that served the request."""
206
207    execution_time_ms: int | None = None
208    """The server-side execution time, in milliseconds."""

Metadata describing how an Agents connector action was executed.

connector_instance_id: str | None = None

The connector instance that served the request.

execution_time_ms: int | None = None

The server-side execution time, in milliseconds.

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[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) -> 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.

class AgentSkill:
26class AgentSkill:
27    """A skill on the Airbyte Agents platform.
28
29    Get one from `AgentWorkspace.get_skill()` or `AgentWorkspace.list_skills()` rather than
30    constructing it directly.
31    """
32
33    def __init__(
34        self,
35        skill_id: str,
36        *,
37        credentials: _AirbyteCredentials,
38        workspace_id: str | None = None,
39        info: AgentSkillInfo | None = None,
40    ) -> None:
41        """Initialize an `AgentSkill`. Prefer `AgentWorkspace.get_skill()`."""
42        self.skill_id = skill_id
43        """The skill ID."""
44
45        self._credentials = credentials
46        self._workspace_id = workspace_id
47        self._info = info
48
49    @property
50    def info(self) -> AgentSkillInfo:
51        """The skill's metadata, fetched from the Agents API if not already known."""
52        if self._info is None:
53            self._info = self.read_docs().metadata
54        return self._info
55
56    @property
57    def title(self) -> str | None:
58        """The human-readable skill title."""
59        return self.info.title
60
61    @property
62    def kind(self) -> str | None:
63        """The skill category, for example `static` or `connector_source`."""
64        return self.info.kind
65
66    def read_docs(self, *, section: str | None = None) -> AgentSkillDocs:
67        """Read this skill's docs, optionally scoped to a single section.
68
69        Omit `section` for metadata, guidance, and the outline of available sections, or
70        pass an exact section `id` from the outline to read that section.
71        """
72        docs = AgentSkillDocs.model_validate(
73            _api_util.read_agent_skill_docs(
74                skill_id=self.skill_id,
75                credentials=self._credentials,
76                organization_id=self._credentials.organization_id,
77                workspace_id=self._workspace_id,
78                section=section,
79            )
80        )
81        if self._info is None:
82            self._info = docs.metadata
83        return docs

A skill on the Airbyte Agents platform.

Get one from AgentWorkspace.get_skill() or AgentWorkspace.list_skills() rather than constructing it directly.

AgentSkill( skill_id: str, *, credentials: airbyte.cloud._credentials._AirbyteCredentials, workspace_id: str | None = None, info: AgentSkillInfo | None = None)
33    def __init__(
34        self,
35        skill_id: str,
36        *,
37        credentials: _AirbyteCredentials,
38        workspace_id: str | None = None,
39        info: AgentSkillInfo | None = None,
40    ) -> None:
41        """Initialize an `AgentSkill`. Prefer `AgentWorkspace.get_skill()`."""
42        self.skill_id = skill_id
43        """The skill ID."""
44
45        self._credentials = credentials
46        self._workspace_id = workspace_id
47        self._info = info

Initialize an AgentSkill. Prefer AgentWorkspace.get_skill().

skill_id

The skill ID.

info: AgentSkillInfo
49    @property
50    def info(self) -> AgentSkillInfo:
51        """The skill's metadata, fetched from the Agents API if not already known."""
52        if self._info is None:
53            self._info = self.read_docs().metadata
54        return self._info

The skill's metadata, fetched from the Agents API if not already known.

title: str | None
56    @property
57    def title(self) -> str | None:
58        """The human-readable skill title."""
59        return self.info.title

The human-readable skill title.

kind: str | None
61    @property
62    def kind(self) -> str | None:
63        """The skill category, for example `static` or `connector_source`."""
64        return self.info.kind

The skill category, for example static or connector_source.

def read_docs( self, *, section: str | None = None) -> AgentSkillDocs:
66    def read_docs(self, *, section: str | None = None) -> AgentSkillDocs:
67        """Read this skill's docs, optionally scoped to a single section.
68
69        Omit `section` for metadata, guidance, and the outline of available sections, or
70        pass an exact section `id` from the outline to read that section.
71        """
72        docs = AgentSkillDocs.model_validate(
73            _api_util.read_agent_skill_docs(
74                skill_id=self.skill_id,
75                credentials=self._credentials,
76                organization_id=self._credentials.organization_id,
77                workspace_id=self._workspace_id,
78                section=section,
79            )
80        )
81        if self._info is None:
82            self._info = docs.metadata
83        return docs

Read this skill's docs, optionally scoped to a single section.

Omit section for metadata, guidance, and the outline of available sections, or pass an exact section id from the outline to read that section.

class AgentSkillDocs(pydantic.main.BaseModel):
132class AgentSkillDocs(BaseModel):
133    """Documentation for a single skill, as returned by the Agents API."""
134
135    model_config = ConfigDict(extra="allow")
136
137    metadata: AgentSkillInfo
138    """Metadata for the requested skill."""
139
140    outline: list[AgentSkillSection] = Field(default_factory=list)
141    """The sections available for this skill."""
142
143    section_id: str | None = None
144    """The requested section ID, or `None` for the default docs response."""
145
146    content: list[dict[str, Any]] = Field(default_factory=list)
147    """Rendered docs content blocks, such as headings, paragraphs, and code blocks."""

Documentation for a single skill, as returned by the Agents API.

metadata: AgentSkillInfo = PydanticUndefined

Metadata for the requested skill.

outline: list[AgentSkillSection] = PydanticUndefined

The sections available for this skill.

section_id: str | None = None

The requested section ID, or None for the default docs response.

content: list[dict[str, typing.Any]] = PydanticUndefined

Rendered docs content blocks, such as headings, paragraphs, and code blocks.

class AgentSkillInfo(pydantic.main.BaseModel):
78class AgentSkillInfo(BaseModel):
79    """Summary information about a skill, as returned by the Agents API."""
80
81    model_config = ConfigDict(extra="allow")
82
83    id: str
84    """The skill ID. Pass it to `read_skill_docs` to read this skill's docs."""
85
86    kind: str | None = None
87    """The skill category, for example `static` or `connector_source`."""
88
89    title: str | None = None
90    """The human-readable skill title."""
91
92    summary: str | None = None
93    """A short summary of what the skill documents."""
94
95    tags: list[str] = Field(default_factory=list)
96    """Search and categorization tags for the skill."""
97
98    warnings: list[Any] = Field(default_factory=list)
99    """Non-fatal issues reported while building or reading the skill's docs."""

Summary information about a skill, as returned by the Agents API.

id: str = PydanticUndefined

The skill ID. Pass it to read_skill_docs to read this skill's docs.

kind: str | None = None

The skill category, for example static or connector_source.

title: str | None = None

The human-readable skill title.

summary: str | None = None

A short summary of what the skill documents.

tags: list[str] = PydanticUndefined

Search and categorization tags for the skill.

warnings: list[typing.Any] = PydanticUndefined

Non-fatal issues reported while building or reading the skill's docs.

class AgentSkillList(pydantic.main.BaseModel):
102class AgentSkillList(BaseModel):
103    """A page of skills, as returned by the Agents API."""
104
105    model_config = ConfigDict(extra="allow")
106
107    data: list[AgentSkillInfo]
108    """The skills on this page."""
109
110    next_cursor: str | None = None
111    """The cursor to pass as `cursor` to fetch the next page, when one is available."""

A page of skills, as returned by the Agents API.

data: list[AgentSkillInfo] = PydanticUndefined

The skills on this page.

next_cursor: str | None = None

The cursor to pass as cursor to fetch the next page, when one is available.

class AgentSkillSection(pydantic.main.BaseModel):
114class AgentSkillSection(BaseModel):
115    """A section of a skill's docs, as listed in the docs outline."""
116
117    model_config = ConfigDict(extra="allow")
118
119    id: str
120    """The section ID. Pass it as `section` to read this section."""
121
122    title: str | None = None
123    """The human-readable section title."""
124
125    summary: str | None = None
126    """A short summary of the section content."""
127
128    available: bool = True
129    """Whether this section can currently be read."""

A section of a skill's docs, as listed in the docs outline.

id: str = PydanticUndefined

The section ID. Pass it as section to read this section.

title: str | None = None

The human-readable section title.

summary: str | None = None

A short summary of the section content.

available: bool = True

Whether this section can currently be read.

class AgentWorkspace:
 34class AgentWorkspace:
 35    """A workspace on the Airbyte Agents platform.
 36
 37    Airbyte Cloud credentials authenticate against the Agents API, so this class reads the
 38    same `AIRBYTE_CLOUD_*` environment variables as `airbyte.cloud.CloudWorkspace`.
 39
 40    ```python
 41    from airbyte import agents
 42
 43    workspace = agents.AgentWorkspace.from_env()
 44    for connector in workspace.list_connectors():
 45        print(connector.name)
 46    ```
 47    """
 48
 49    def __init__(
 50        self,
 51        *,
 52        workspace_id: str | None = None,
 53        organization_id: str | None = None,
 54        name: str | None = None,
 55        client_id: str | SecretString | None = None,
 56        client_secret: str | SecretString | None = None,
 57        bearer_token: str | SecretString | None = None,
 58        public_api_root: str | None = None,
 59        config_api_root: str | None = None,
 60    ) -> None:
 61        """Initialize an `AgentWorkspace`.
 62
 63        Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are
 64        not passed explicitly. API roots default to the `AIRBYTE_CLOUD_API_URL` /
 65        `AIRBYTE_CLOUD_CONFIG_API_URL` environment variables (public Airbyte Cloud when unset);
 66        custom roots require `AIRBYTE_AGENTS_API_URL`.
 67        """
 68        credentials = _AirbyteCredentials.from_auth(
 69            workspace_id=workspace_id,
 70            organization_id=organization_id,
 71            client_id=client_id,
 72            client_secret=client_secret,
 73            bearer_token=bearer_token,
 74            public_api_root=public_api_root,
 75            config_api_root=config_api_root,
 76            # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env
 77            # fallback, since an env bearer token plus explicit client creds is rejected
 78            # as mutually exclusive auth.
 79            env_vars=not (client_id or client_secret or bearer_token),
 80        )
 81        if not credentials.workspace_id:
 82            raise PyAirbyteInputError(
 83                message="Workspace ID is required.",
 84                guidance=(
 85                    "Provide `workspace_id`, or set the `AIRBYTE_CLOUD_WORKSPACE_ID` "
 86                    "environment variable."
 87                ),
 88            )
 89
 90        self._credentials = credentials
 91
 92        self.workspace_id: str = credentials.workspace_id
 93        """The workspace ID."""
 94
 95        self.organization_id: str | None = credentials.organization_id
 96        """The organization ID, sent to the Agents API when it is known."""
 97
 98        self.name: str | None = name
 99        """The workspace name, when known. Use `get_info()` to fetch it from the API."""
100
101    @classmethod
102    def from_env(
103        cls,
104        workspace_id: str | None = None,
105        *,
106        organization_id: str | None = None,
107    ) -> AgentWorkspace:
108        """Create an `AgentWorkspace` from the `AIRBYTE_CLOUD_*` environment variables.
109
110        The variables used are `AIRBYTE_CLOUD_BEARER_TOKEN` or the
111        `AIRBYTE_CLOUD_CLIENT_ID` and `AIRBYTE_CLOUD_CLIENT_SECRET` pair, along with
112        `AIRBYTE_CLOUD_WORKSPACE_ID` and `AIRBYTE_CLOUD_ORGANIZATION_ID`.
113        """
114        return cls(workspace_id=workspace_id, organization_id=organization_id)
115
116    def get_info(self) -> AgentWorkspaceInfo:
117        """Fetch this workspace from the Agents API.
118
119        A successful call is authoritative proof that the workspace is reachable through
120        the Agents API with these credentials.
121        """
122        return AgentWorkspaceInfo.model_validate(
123            _api_util.get_agent_workspace(
124                workspace_id=self.workspace_id,
125                credentials=self._credentials,
126                organization_id=self.organization_id,
127            )
128        )
129
130    def list_connectors(self) -> list[AgentConnector]:
131        """List the connectors configured in this workspace."""
132        return [
133            AgentConnector(
134                connector_id=info.id,
135                name=info.name,
136                credentials=self._credentials,
137                workspace_id=self.workspace_id,
138            )
139            for info in (
140                AgentConnectorInfo.model_validate(record)
141                for record in _api_util.list_agent_connectors(
142                    workspace_id=self.workspace_id,
143                    credentials=self._credentials,
144                    organization_id=self.organization_id,
145                )
146            )
147        ]
148
149    def list_skills(self) -> list[AgentSkill]:
150        """List all skills available to this workspace, following pagination."""
151        return [
152            AgentSkill(
153                skill_id=info.id,
154                credentials=self._credentials,
155                workspace_id=self.workspace_id,
156                info=info,
157            )
158            for info in _skills.iter_skills(
159                credentials=self._credentials,
160                workspace_id=self.workspace_id,
161            )
162        ]
163
164    def get_skill(self, skill_id: str) -> AgentSkill:
165        """Get a skill by ID, without calling the Agents API."""
166        return AgentSkill(
167            skill_id,
168            credentials=self._credentials,
169            workspace_id=self.workspace_id,
170        )
171
172    def read_skill_docs(
173        self,
174        skill_id: str,
175        *,
176        section: str | None = None,
177    ) -> AgentSkillDocs:
178        """Read a skill's docs, optionally scoped to a single section.
179
180        Omit `section` for metadata, guidance, and the outline of available sections, or
181        pass an exact section `id` from the outline to read that section. Connector usage
182        docs use the `docs_skill_id` reported by `AgentConnector.inspect()`.
183        """
184        return self.get_skill(skill_id).read_docs(section=section)
185
186    def get_connector(
187        self,
188        id_or_name: str | None = None,
189        /,
190        *,
191        id: str | None = None,  # noqa: A002  # Shadows `id` deliberately, as a short alias.
192        connector_id: str | None = None,
193        name: str | None = None,
194    ) -> AgentConnector:
195        """Get a connector in this workspace, by ID or by name.
196
197        Pass a single positional value to look the connector up by either its ID or its
198        name, or name the argument to be explicit: `id` and `connector_id` are synonyms,
199        so pass whichever reads better.
200
201        Lookup by an explicit ID does not call the Agents API. Every other form lists the
202        workspace's connectors and matches on ID first, then on an exact name, then on a
203        unique substring, so `name="GitHub"` finds a connector named
204        `GitHub - <workspace_id>`. Name matching is case-insensitive.
205        """
206        lookup = _resolve_connector_lookup(
207            id_or_name,
208            id=id,
209            connector_id=connector_id,
210            name=name,
211        )
212
213        if lookup.connector_id and not lookup.name:
214            return AgentConnector(
215                connector_id=lookup.connector_id,
216                credentials=self._credentials,
217                workspace_id=self.workspace_id,
218            )
219
220        connectors = self.list_connectors()
221        if lookup.connector_id:
222            id_matches = [
223                connector
224                for connector in connectors
225                if connector.connector_id == lookup.connector_id
226            ]
227            if id_matches:
228                return id_matches[0]
229
230        name_lower = (lookup.name or "").lower()
231        matches = [
232            connector
233            for connector in connectors
234            if connector.name and connector.name.lower() == name_lower
235        ] or [
236            connector
237            for connector in connectors
238            if connector.name and name_lower in connector.name.lower()
239        ]
240        if not matches:
241            raise AirbyteError(
242                message="No connector found with the given ID or name.",
243                guidance="Use `list_connectors()` to see the available connectors.",
244                context={"lookup": lookup.name, "workspace_id": self.workspace_id},
245            )
246        if len(matches) > 1:
247            raise AirbyteError(
248                message="Multiple connectors matched the given name.",
249                guidance="Pass `connector_id`, or a name that matches only one connector.",
250                context={
251                    "name": lookup.name,
252                    "matched_names": [connector.name for connector in matches],
253                },
254            )
255        return matches[0]
256
257    def as_cloud_workspace(self) -> CloudWorkspace:
258        """Return this workspace as an `airbyte.cloud.CloudWorkspace`.
259
260        Every Agents workspace is also a Cloud workspace, so this conversion always
261        succeeds without calling either API.
262        """
263        return CloudWorkspace(
264            workspace_id=self.workspace_id,
265            client_id=self._credentials.client_id,
266            client_secret=self._credentials.client_secret,
267            bearer_token=self._credentials.bearer_token,
268            api_root=self._credentials.public_api_root,
269            config_api_root=self._credentials.config_api_root,
270        )
271
272    @classmethod
273    def from_cloud_workspace(
274        cls,
275        cloud_workspace: CloudWorkspace,
276        *,
277        organization_id: str | None = None,
278        verify: bool = True,
279    ) -> AgentWorkspace:
280        """Return a Cloud workspace as an `AgentWorkspace`.
281
282        Cloud workspace IDs are also Agents workspace IDs, but not every Cloud workspace is
283        reachable through the Agents API: the organization needs an Airbyte Agents
284        subscription. By default this is verified by fetching the workspace from the Agents
285        API, which raises `AirbyteError` when it is not eligible. Pass `verify=False` to
286        skip that call.
287
288        Raises `AirbyteAgentsUnavailableError` when the Cloud workspace uses non-public Cloud API
289        roots unless `AIRBYTE_AGENTS_API_URL` is set; the Cloud API roots are carried over so the
290        token exchange stays on the same deployment.
291        """
292        _api_util.check_public_cloud_api_roots(
293            cloud_workspace._credentials,  # noqa: SLF001  # Same-domain conversion.
294        )
295        workspace = cls(
296            workspace_id=cloud_workspace.workspace_id,
297            organization_id=organization_id,
298            client_id=cloud_workspace.client_id,
299            client_secret=cloud_workspace.client_secret,
300            bearer_token=cloud_workspace.bearer_token,
301            public_api_root=cloud_workspace._credentials.public_api_root,  # noqa: SLF001
302            config_api_root=cloud_workspace._credentials.config_api_root,  # noqa: SLF001
303        )
304        if verify:
305            workspace.get_info()
306        return workspace

A workspace on the Airbyte Agents platform.

Airbyte Cloud credentials authenticate against the Agents API, so this class reads the same AIRBYTE_CLOUD_* environment variables as airbyte.cloud.CloudWorkspace.

from airbyte import agents

workspace = agents.AgentWorkspace.from_env()
for connector in workspace.list_connectors():
    print(connector.name)
AgentWorkspace( *, workspace_id: str | None = None, organization_id: str | None = None, name: 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)
49    def __init__(
50        self,
51        *,
52        workspace_id: str | None = None,
53        organization_id: str | None = None,
54        name: str | None = None,
55        client_id: str | SecretString | None = None,
56        client_secret: str | SecretString | None = None,
57        bearer_token: str | SecretString | None = None,
58        public_api_root: str | None = None,
59        config_api_root: str | None = None,
60    ) -> None:
61        """Initialize an `AgentWorkspace`.
62
63        Credentials fall back to the `AIRBYTE_CLOUD_*` environment variables when they are
64        not passed explicitly. API roots default to the `AIRBYTE_CLOUD_API_URL` /
65        `AIRBYTE_CLOUD_CONFIG_API_URL` environment variables (public Airbyte Cloud when unset);
66        custom roots require `AIRBYTE_AGENTS_API_URL`.
67        """
68        credentials = _AirbyteCredentials.from_auth(
69            workspace_id=workspace_id,
70            organization_id=organization_id,
71            client_id=client_id,
72            client_secret=client_secret,
73            bearer_token=bearer_token,
74            public_api_root=public_api_root,
75            config_api_root=config_api_root,
76            # Mirrors `CloudWorkspace.__init__`: any explicit credential disables env
77            # fallback, since an env bearer token plus explicit client creds is rejected
78            # as mutually exclusive auth.
79            env_vars=not (client_id or client_secret or bearer_token),
80        )
81        if not credentials.workspace_id:
82            raise PyAirbyteInputError(
83                message="Workspace ID is required.",
84                guidance=(
85                    "Provide `workspace_id`, or set the `AIRBYTE_CLOUD_WORKSPACE_ID` "
86                    "environment variable."
87                ),
88            )
89
90        self._credentials = credentials
91
92        self.workspace_id: str = credentials.workspace_id
93        """The workspace ID."""
94
95        self.organization_id: str | None = credentials.organization_id
96        """The organization ID, sent to the Agents API when it is known."""
97
98        self.name: str | None = name
99        """The workspace name, when known. Use `get_info()` to fetch it from the API."""

Initialize an AgentWorkspace.

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.

workspace_id: str

The workspace ID.

organization_id: str | None

The organization ID, sent to the Agents API when it is known.

name: str | None

The workspace name, when known. Use get_info() to fetch it from the API.

@classmethod
def from_env( cls, workspace_id: str | None = None, *, organization_id: str | None = None) -> AgentWorkspace:
101    @classmethod
102    def from_env(
103        cls,
104        workspace_id: str | None = None,
105        *,
106        organization_id: str | None = None,
107    ) -> AgentWorkspace:
108        """Create an `AgentWorkspace` from the `AIRBYTE_CLOUD_*` environment variables.
109
110        The variables used are `AIRBYTE_CLOUD_BEARER_TOKEN` or the
111        `AIRBYTE_CLOUD_CLIENT_ID` and `AIRBYTE_CLOUD_CLIENT_SECRET` pair, along with
112        `AIRBYTE_CLOUD_WORKSPACE_ID` and `AIRBYTE_CLOUD_ORGANIZATION_ID`.
113        """
114        return cls(workspace_id=workspace_id, organization_id=organization_id)

Create an AgentWorkspace from the AIRBYTE_CLOUD_* environment variables.

The variables used are AIRBYTE_CLOUD_BEARER_TOKEN or the AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET pair, along with AIRBYTE_CLOUD_WORKSPACE_ID and AIRBYTE_CLOUD_ORGANIZATION_ID.

def get_info(self) -> AgentWorkspaceInfo:
116    def get_info(self) -> AgentWorkspaceInfo:
117        """Fetch this workspace from the Agents API.
118
119        A successful call is authoritative proof that the workspace is reachable through
120        the Agents API with these credentials.
121        """
122        return AgentWorkspaceInfo.model_validate(
123            _api_util.get_agent_workspace(
124                workspace_id=self.workspace_id,
125                credentials=self._credentials,
126                organization_id=self.organization_id,
127            )
128        )

Fetch this workspace from the Agents API.

A successful call is authoritative proof that the workspace is reachable through the Agents API with these credentials.

def list_connectors(self) -> list[AgentConnector]:
130    def list_connectors(self) -> list[AgentConnector]:
131        """List the connectors configured in this workspace."""
132        return [
133            AgentConnector(
134                connector_id=info.id,
135                name=info.name,
136                credentials=self._credentials,
137                workspace_id=self.workspace_id,
138            )
139            for info in (
140                AgentConnectorInfo.model_validate(record)
141                for record in _api_util.list_agent_connectors(
142                    workspace_id=self.workspace_id,
143                    credentials=self._credentials,
144                    organization_id=self.organization_id,
145                )
146            )
147        ]

List the connectors configured in this workspace.

def list_skills(self) -> list[AgentSkill]:
149    def list_skills(self) -> list[AgentSkill]:
150        """List all skills available to this workspace, following pagination."""
151        return [
152            AgentSkill(
153                skill_id=info.id,
154                credentials=self._credentials,
155                workspace_id=self.workspace_id,
156                info=info,
157            )
158            for info in _skills.iter_skills(
159                credentials=self._credentials,
160                workspace_id=self.workspace_id,
161            )
162        ]

List all skills available to this workspace, following pagination.

def get_skill(self, skill_id: str) -> AgentSkill:
164    def get_skill(self, skill_id: str) -> AgentSkill:
165        """Get a skill by ID, without calling the Agents API."""
166        return AgentSkill(
167            skill_id,
168            credentials=self._credentials,
169            workspace_id=self.workspace_id,
170        )

Get a skill by ID, without calling the Agents API.

def read_skill_docs( self, skill_id: str, *, section: str | None = None) -> AgentSkillDocs:
172    def read_skill_docs(
173        self,
174        skill_id: str,
175        *,
176        section: str | None = None,
177    ) -> AgentSkillDocs:
178        """Read a skill's docs, optionally scoped to a single section.
179
180        Omit `section` for metadata, guidance, and the outline of available sections, or
181        pass an exact section `id` from the outline to read that section. Connector usage
182        docs use the `docs_skill_id` reported by `AgentConnector.inspect()`.
183        """
184        return self.get_skill(skill_id).read_docs(section=section)

Read a skill's docs, optionally scoped to a single section.

Omit section for metadata, guidance, and the outline of available sections, or pass an exact section id from the outline to read that section. Connector usage docs use the docs_skill_id reported by AgentConnector.inspect().

def get_connector( self, id_or_name: str | None = None, /, *, id: str | None = None, connector_id: str | None = None, name: str | None = None) -> AgentConnector:
186    def get_connector(
187        self,
188        id_or_name: str | None = None,
189        /,
190        *,
191        id: str | None = None,  # noqa: A002  # Shadows `id` deliberately, as a short alias.
192        connector_id: str | None = None,
193        name: str | None = None,
194    ) -> AgentConnector:
195        """Get a connector in this workspace, by ID or by name.
196
197        Pass a single positional value to look the connector up by either its ID or its
198        name, or name the argument to be explicit: `id` and `connector_id` are synonyms,
199        so pass whichever reads better.
200
201        Lookup by an explicit ID does not call the Agents API. Every other form lists the
202        workspace's connectors and matches on ID first, then on an exact name, then on a
203        unique substring, so `name="GitHub"` finds a connector named
204        `GitHub - <workspace_id>`. Name matching is case-insensitive.
205        """
206        lookup = _resolve_connector_lookup(
207            id_or_name,
208            id=id,
209            connector_id=connector_id,
210            name=name,
211        )
212
213        if lookup.connector_id and not lookup.name:
214            return AgentConnector(
215                connector_id=lookup.connector_id,
216                credentials=self._credentials,
217                workspace_id=self.workspace_id,
218            )
219
220        connectors = self.list_connectors()
221        if lookup.connector_id:
222            id_matches = [
223                connector
224                for connector in connectors
225                if connector.connector_id == lookup.connector_id
226            ]
227            if id_matches:
228                return id_matches[0]
229
230        name_lower = (lookup.name or "").lower()
231        matches = [
232            connector
233            for connector in connectors
234            if connector.name and connector.name.lower() == name_lower
235        ] or [
236            connector
237            for connector in connectors
238            if connector.name and name_lower in connector.name.lower()
239        ]
240        if not matches:
241            raise AirbyteError(
242                message="No connector found with the given ID or name.",
243                guidance="Use `list_connectors()` to see the available connectors.",
244                context={"lookup": lookup.name, "workspace_id": self.workspace_id},
245            )
246        if len(matches) > 1:
247            raise AirbyteError(
248                message="Multiple connectors matched the given name.",
249                guidance="Pass `connector_id`, or a name that matches only one connector.",
250                context={
251                    "name": lookup.name,
252                    "matched_names": [connector.name for connector in matches],
253                },
254            )
255        return matches[0]

Get a connector in this workspace, by ID or by name.

Pass a single positional value to look the connector up by either its ID or its name, or name the argument to be explicit: id and connector_id are synonyms, so pass whichever reads better.

Lookup by an explicit ID does not call the Agents API. Every other form lists the workspace's connectors and matches on ID first, then on an exact name, then on a unique substring, so name="GitHub" finds a connector named GitHub - <workspace_id>. Name matching is case-insensitive.

def as_cloud_workspace(self) -> airbyte.cloud.CloudWorkspace:
257    def as_cloud_workspace(self) -> CloudWorkspace:
258        """Return this workspace as an `airbyte.cloud.CloudWorkspace`.
259
260        Every Agents workspace is also a Cloud workspace, so this conversion always
261        succeeds without calling either API.
262        """
263        return CloudWorkspace(
264            workspace_id=self.workspace_id,
265            client_id=self._credentials.client_id,
266            client_secret=self._credentials.client_secret,
267            bearer_token=self._credentials.bearer_token,
268            api_root=self._credentials.public_api_root,
269            config_api_root=self._credentials.config_api_root,
270        )

Return this workspace as an airbyte.cloud.CloudWorkspace.

Every Agents workspace is also a Cloud workspace, so this conversion always succeeds without calling either API.

@classmethod
def from_cloud_workspace( cls, cloud_workspace: airbyte.cloud.CloudWorkspace, *, organization_id: str | None = None, verify: bool = True) -> AgentWorkspace:
272    @classmethod
273    def from_cloud_workspace(
274        cls,
275        cloud_workspace: CloudWorkspace,
276        *,
277        organization_id: str | None = None,
278        verify: bool = True,
279    ) -> AgentWorkspace:
280        """Return a Cloud workspace as an `AgentWorkspace`.
281
282        Cloud workspace IDs are also Agents workspace IDs, but not every Cloud workspace is
283        reachable through the Agents API: the organization needs an Airbyte Agents
284        subscription. By default this is verified by fetching the workspace from the Agents
285        API, which raises `AirbyteError` when it is not eligible. Pass `verify=False` to
286        skip that call.
287
288        Raises `AirbyteAgentsUnavailableError` when the Cloud workspace uses non-public Cloud API
289        roots unless `AIRBYTE_AGENTS_API_URL` is set; the Cloud API roots are carried over so the
290        token exchange stays on the same deployment.
291        """
292        _api_util.check_public_cloud_api_roots(
293            cloud_workspace._credentials,  # noqa: SLF001  # Same-domain conversion.
294        )
295        workspace = cls(
296            workspace_id=cloud_workspace.workspace_id,
297            organization_id=organization_id,
298            client_id=cloud_workspace.client_id,
299            client_secret=cloud_workspace.client_secret,
300            bearer_token=cloud_workspace.bearer_token,
301            public_api_root=cloud_workspace._credentials.public_api_root,  # noqa: SLF001
302            config_api_root=cloud_workspace._credentials.config_api_root,  # noqa: SLF001
303        )
304        if verify:
305            workspace.get_info()
306        return workspace

Return a Cloud workspace as an AgentWorkspace.

Cloud workspace IDs are also Agents workspace IDs, but not every Cloud workspace is reachable through the Agents API: the organization needs an Airbyte Agents subscription. By default this is verified by fetching the workspace from the Agents API, which raises AirbyteError when it is not eligible. Pass verify=False to skip that call.

Raises AirbyteAgentsUnavailableError when the Cloud workspace 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.

class AgentWorkspaceInfo(pydantic.main.BaseModel):
24class AgentWorkspaceInfo(BaseModel):
25    """Summary information about a workspace, as returned by the Agents API."""
26
27    model_config = ConfigDict(extra="allow")
28
29    id: str
30    """The workspace ID."""
31
32    name: str | None = None
33    """The workspace name."""
34
35    organization_id: str | None = None
36    """The ID of the organization the workspace belongs to."""
37
38    status: str | None = None
39    """The workspace status, for example `active`."""

Summary information about a workspace, as returned by the Agents API.

id: str = PydanticUndefined

The workspace ID.

name: str | None = None

The workspace name.

organization_id: str | None = None

The ID of the organization the workspace belongs to.

status: str | None = None

The workspace status, for example active.