> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xpander.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Use Pre-built Skills

> How skills selected in Xpander Chat show up as functions in code, and how to invoke them

xpander.ai ships pre-built skills (Slack, Gmail, GitHub, Salesforce, and more) that expose their operations as callable functions. You select which ones an agent can use in Xpander Chat; the SDK then exposes them as ready-to-call functions on the loaded `Agent`. In the SDK a skill is a `Tool`; `agent.tools` lists them. No manual fetching, no schema conversion, no client setup.

## Prerequisites

* **Complete the [Quickstart](/developers/quickstart)** so the CLI, SDK, and `xpander login` are already set up.
* **Python 3.12+** for the local handler.

## 1. Set up skills in Xpander Chat

Setup is a one-time UI flow: use **Add skill** in the agent's settings, pick a skill, connect your account if it asks, and attach the operations you want (see [Skills on an agent](/use/skills) for the full flow). Once published, the skill's operations are live on every loaded `Agent` instance and reachable from any agent loop you bind them into.

<Card title="Set up skills in Xpander Chat" icon="plug" href="/use/skills">
  Pick a skill, connect your account, attach operations to your agent.
</Card>

<img src="https://mintcdn.com/xpanderai-099931d1/ZExbmT2Yknjn6o_V/images/image-48.png?fit=max&auto=format&n=ZExbmT2Yknjn6o_V&q=85&s=8907d2a7a1f617f415183893285a994a" alt="Skills attached to an agent in Xpander Chat, with each connection and its access level" width="700" height="876" data-path="images/image-48.png" />

Everything below assumes you've done this and your agent has at least one skill attached.

## 2. List all available skills

Once a skill is attached, your agent has access to it. xpander's kinds of skill are described on the [catalog](/connectors#four-kinds-of-skill); from the SDK they arrive as three groups:

* **Catalog skills**: every operation you selected from a pre-built skill (Slack, Gmail, GitHub, etc.)
* **Custom skills**: Python functions you decorated with `@register_tool`
* **MCP skills**: skills from any MCP endpoint you've attached

Use `agent.tools.list` to enumerate all of them:

```python theme={"dark"}
from xpander_sdk import Agents

agent = await Agents().aget(agent_id="agt_01H...")

# Every tool the agent can call, mixed sources
for tool in agent.tools.list:
    print(tool.name, tool.is_local, tool.description[:80])
```

This is what you'd use to:

* Build a UI that shows the user which capabilities an agent has before they prompt it.
* Sanity-check after a publish that the skill you just attached actually shows up.
* Filter the skill list at runtime (e.g. drop write-operations when running in a read-only context).

### Inspect individual skills

If you want to inspect the exact shape that's about to be handed to the agent loop (debugging "are my `FunctionTool` objects correct?", "did the LangChain callables get their docstrings?"), enumerate the loop-specific list instead:

<Tabs>
  <Tab title="Agno">
    The args dict from `Backend.aget_args()` carries the resolved skills list. Iterate `args["tools"]` to see exactly what Agno will receive:

    ```python theme={"dark"}
    from xpander_sdk import Backend

    backend = Backend()
    args = await backend.aget_args(agent_id="agt_01H...", task=task)
    for tool in args["tools"]:
        print(tool.__name__, tool.__doc__[:80] if tool.__doc__ else "")
    ```
  </Tab>

  <Tab title="OpenAI Agents SDK">
    `agent.openai_agents_sdk_tools` returns `FunctionTool` objects ready for the OpenAI runner:

    ```python theme={"dark"}
    from xpander_sdk import Agents

    xpander_agent = await Agents().aget(agent_id="agt_01H...")
    for tool in xpander_agent.openai_agents_sdk_tools:
        # FunctionTool exposes .name and .description
        print(tool.name, tool.description[:80])
    ```
  </Tab>

  <Tab title="LangChain">
    `agent.tools.functions` returns plain Python callables with `payload: <PydanticModel>` parameters and LLM-friendly docstrings:

    ```python theme={"dark"}
    from xpander_sdk import Agents

    xpander_agent = await Agents().aget(agent_id="agt_01H...")
    for fn in xpander_agent.tools.functions:
        print(fn.__name__, (fn.__doc__ or "")[:80])
    ```
  </Tab>

  <Tab title="AWS Strands">
    `agent.strands_tools` returns the list ready for Strands' `tools=` arg:

    ```python theme={"dark"}
    from xpander_sdk import Agents

    xpander_agent = await Agents().aget(agent_id="agt_01H...")
    for tool in xpander_agent.strands_tools:
        print(tool.__name__, (tool.__doc__ or "")[:80])
    ```
  </Tab>
</Tabs>

Three properties you'll reach for most often:

| Property | Returns | What it's for |
| - | - | - |
| `agent.tools.list` | `list[Tool]` | Canonical enumeration. Each `Tool` carries `id`, `name`, `description`, `parameters` (raw JSON schema), and a Pydantic `schema`. |
| `agent.tools.functions` | `list[Callable]` | Normalized callables, one per skill, with a `payload: <PydanticModel>` parameter and an LLM-friendly docstring. Bind them straight into LangChain, OpenAI Agents SDK, or any agent loop that takes plain Python functions. |
| `agent.tools.get_tool_by_id(id)` / `get_tool_by_name(name)` | `Tool` | Look up a single skill when you want to invoke it by hand. |

## 3. Bind skills into the agent loop

Listing skills tells you what the agent can do. Binding them is what gets the LLM to actually call them. Each agent loop (Agno, OpenAI Agents SDK, LangChain, AWS Strands) wants its functions in its own shape, and the xpander `Agent` exposes one property per supported loop, computed off the same underlying skill list:

<Tabs>
  <Tab title="Agno">
    ```python xpander_handler.py highlight={10,12} theme={"dark"}
    from dotenv import load_dotenv
    load_dotenv()

    from xpander_sdk import on_task, Task, Backend
    from agno.agent import Agent

    @on_task
    async def handler(task: Task) -> Task:
        backend = Backend(configuration=task.configuration)
        agno_args = await backend.aget_args(task=task)

        agno_agent = Agent(**agno_args)
        result = await agno_agent.arun(
            input=task.to_message(),
            files=task.get_files(),
            images=task.get_images(),
        )

        task.result = result.content
        return task
    ```

    See the [Agno page](/developers/frameworks/agno) for the full walkthrough.
  </Tab>

  <Tab title="OpenAI Agents SDK">
    ```python xpander_handler.py highlight={16} theme={"dark"}
    from dotenv import load_dotenv
    load_dotenv()

    from xpander_sdk import on_task, Task, Agents
    from agents import Agent as OpenAIAgent, Runner

    @on_task
    async def handler(task: Task) -> Task:
        xpander_agent = await Agents(configuration=task.configuration).aget(
            agent_id=task.agent_id
        )

        oa_agent = OpenAIAgent(
            name=xpander_agent.name,
            instructions=xpander_agent.instructions.full,
            tools=xpander_agent.openai_agents_sdk_tools,
            model=xpander_agent.model_name,
        )

        result = await Runner.run(oa_agent, input=task.to_message())
        task.result = result.final_output
        return task
    ```

    See the [OpenAI Agents SDK page](/developers/frameworks/openai-agents) for the full walkthrough.
  </Tab>

  <Tab title="LangChain">
    ```python xpander_handler.py highlight={16} theme={"dark"}
    from dotenv import load_dotenv
    load_dotenv()

    from xpander_sdk import on_task, Task, Agents
    from langchain_openai import ChatOpenAI
    from langgraph.prebuilt import create_react_agent

    @on_task
    async def handler(task: Task) -> Task:
        xpander_agent = await Agents(configuration=task.configuration).aget(
            agent_id=task.agent_id
        )

        react = create_react_agent(
            model=ChatOpenAI(model=xpander_agent.model_name),
            tools=xpander_agent.tools.functions,
            state_modifier=xpander_agent.instructions.full,
        )

        response = await react.ainvoke({
            "messages": [("user", task.to_message())],
        })
        task.result = response["messages"][-1].content
        return task
    ```

    See the [LangChain page](/developers/frameworks/langchain) for the full walkthrough.
  </Tab>

  <Tab title="AWS Strands">
    ```python xpander_handler.py highlight={16} theme={"dark"}
    from dotenv import load_dotenv
    load_dotenv()

    from xpander_sdk import on_task, Task, Agents
    from strands.agent import Agent as StrandsAgent

    @on_task
    async def handler(task: Task) -> Task:
        xpander_agent = await Agents(configuration=task.configuration).aget(
            agent_id=task.agent_id
        )

        strands_agent = StrandsAgent(
            name=xpander_agent.name,
            system_prompt=xpander_agent.instructions.full,
            tools=xpander_agent.strands_tools,
            model=xpander_agent.model_name,
        )

        result = await strands_agent.ainvoke_async(task.to_message())
        task.result = str(result)
        return task
    ```

    See the [AWS Strands page](/developers/frameworks/aws-strands) for the full walkthrough.
  </Tab>
</Tabs>

## 4. Invoke a skill yourself (optional)

To call a skill outside the agent loop:

Use this to:

* Run an admin or migration script that calls a skill once (send a Slack announcement, archive a Salesforce record) without standing up a full agent loop.
* Backfill or batch-process by iterating over a queue of inputs and firing the same skill against each.
* Test a skill's payload shape end-to-end before exposing it to the LLM.

```python theme={"dark"}
from xpander_sdk import Agents

agent = await Agents().aget(agent_id="agt_01H...")
email_tool = agent.tools.get_tool_by_id(
    "XpanderEmailServiceSendEmailWithHtmlOrTextContent"
)

result = await agent.ainvoke_tool(
    tool=email_tool,
    payload={
        "body_params": {
            "subject": "Hello from xpander",
            "body_html": "<p>Hi there!</p>",
            "to": ["recipient@example.com"],
        },
        "path_params": {},
        "query_params": {},
    },
)

print(result.is_success, result.result)
```

What this means in practice:

1. **`agent.tools.get_tool_by_id(...)`** looks up the skill by its stable identifier. Use `get_tool_by_name(...)` if you have the human-readable name instead.
2. **The `payload` mirrors the skill's JSON schema.** Catalog skills typically have `body_params`, `path_params`, and `query_params` as the top-level keys, mapping to the underlying API's request shape. Inspect `tool.parameters` if you need the schema.
3. **`ToolInvocationResult`** comes back with `is_success` (bool) and `result` (the raw response). Check `is_success` before consuming `result`.

The sync form `agent.invoke_tool(...)` exists with the same signature.

## 5. Pass per-request context to every skill call

When the same agent definition serves many tenants, customers, or users, you usually need to stamp something onto every skill call (a tenant ID, a customer ID, a request ID, an OAuth token override) without putting it in the prompt or hardcoding it on the agent. That's what `tool_call_payload_extension` is for. It deep-merges into every skill call's payload for the lifetime of the task:

```python theme={"dark"}
task = await agent.acreate_task(
    prompt="Summarize this customer's recent activity",
    # Every connector call inside this task gets tenant_id merged into body_params.
    tool_call_payload_extension={"body_params": {"tenant_id": "acme-corp"}},
)
```

Common shapes:

* **Multi-tenant**: stamp the tenant ID on every downstream call so skill requests scope correctly.
* **Per-user OAuth**: pass a user-scoped token override into `body_params` or a custom auth header.
* **Traceability**: add a request ID to every skill call so you can follow a single user's actions across systems.

The same dict surface is accepted on three call sites, scoped differently:

| Where you pass it | Argument | Scope | When to use |
| - | - | - | - |
| `agent.acreate_task(...)` | `tool_call_payload_extension: Optional[dict] = None` | Every skill call inside the task. | The default. Stamping context that's the same for the whole task (tenant ID, request ID, user token). |
| `Backend.ainvoke_agent(...)` | `tool_call_payload_extension: Optional[dict] = None` | Every skill call inside the task implicitly created by the invoke. | When you're invoking an agent through `Backend` rather than creating a task explicitly. |
| `agent.ainvoke_tool(...)` | `payload_extension: Optional[Dict] = {}` | One skill invocation. | Reaching past the LLM to call a specific skill by hand and you only want to stamp this call. |

The merge is deep, not shallow. Mirror the skill's payload shape (`body_params`, `path_params`, `query_params`) when extending, otherwise the keys land at the wrong level and the skill ignores them.

## Troubleshooting

<AccordionGroup>
  <Accordion title="`agent.tools.list` is empty">
    The agent has no skills attached, or the skills you attached aren't published yet. Open the agent in Xpander Chat, check the **Skills** row for at least one entry, and click **Publish** if the agent has unpublished changes. Then reload the agent: `agent = await Agents().aget(agent_id=...)`.
  </Accordion>

  <Accordion title="Skill call returns `is_success=False` with a 4xx body error">
    The payload doesn't match the skill's expected shape. Inspect `tool.parameters` (raw JSON schema) and confirm `body_params`, `path_params`, and `query_params` line up with the underlying API. The Pydantic `tool.schema` is also useful for type-checking the payload before sending.
  </Accordion>

  <Accordion title="`tool_call_payload_extension` doesn't show up in skill calls">
    The extension is deep-merged, so it only adds keys at the same path. If you pass `{"body_params": {"tenant_id": "..."}}`, it merges into the skill's `body_params`; it won't appear in `query_params`. Mirror the skill's payload structure when extending.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Custom skills" icon="wrench" href="/developers/tools/custom-tools">
    Add your own Python functions alongside catalog skills with `@register_tool`.
  </Card>

  <Card title="Skill call hooks" icon="bolt" href="/developers/tools/tool-hooks">
    Observe, log, and rewrite every skill call across the agent's lifetime.
  </Card>

  <Card title="Output Response Filtering" icon="filter" href="/developers/tools/output-response-filtering">
    How large skill responses get filtered before reaching the LLM.
  </Card>

  <Card title="Skills catalog" icon="grid" href="/connectors">
    Browse all pre-built skills.
  </Card>

  <Card title="Set up skills in Xpander Chat" icon="plug" href="/use/skills">
    The UI walkthrough: connected accounts, OAuth, attaching operations.
  </Card>

  <Card title="Agent loops" icon="cubes" href="/developers/frameworks">
    How `agent.tools.functions`, `openai_agents_sdk_tools`, and `strands_tools` map onto Agno, OpenAI Agents SDK, LangChain, and AWS Strands.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.