Skip to content

Skills Module

The skills module provides functionality for managing global skills in Forra.

SkillsAPI

Source code in forrasdk/api/skills.py
class SkillsAPI:
    def __init__(self, base_url: str, headers: dict, retry_strategy: Callable) -> None:
        self._base_url = base_url
        self._headers = headers
        self._retry_strategy = retry_strategy

    def list_all(
        self, search: Optional[str] = None, order_by: Optional[str] = None
    ) -> list[dict]:
        """
        List all global skills from the Scout API.

        Args:
            search (Optional[str]): Optional search query to filter skills
            order_by (Optional[str]): Optional ordering parameter

        Returns:
            list[dict]: List of skill dictionaries containing id, name, description, type, functions_status, etc.

        Raises:
            Exception: If there is an error during the API request.
        """
        params = {}
        if search:
            params["search"] = search
        if order_by:
            params["orderBy"] = order_by

        response, status_code = RequestUtils.get(
            url=f"{self._base_url}/api/skills/global",
            headers=self._headers,
            params=params if params else None,
            retry_strategy=self._retry_strategy,
        )

        if status_code >= 200 and status_code < 300:
            return response if isinstance(response, list) else []
        else:
            error_msg = f"Failed to list skills (status {status_code})"
            if isinstance(response, dict):
                error_detail = response.get("message", "")
                if error_detail:
                    error_msg += f": {error_detail}"
            raise Exception(error_msg)

    def get(self, skill_id: str) -> dict:
        """
        Get a specific global skill by ID.

        Args:
            skill_id (str): The ID of the skill to retrieve

        Returns:
            dict: Skill dictionary containing id, name, description, type, functions_status, etc.

        Raises:
            Exception: If there is an error during the API request or skill not found.
        """
        response, status_code = RequestUtils.get(
            url=f"{self._base_url}/api/skills/global/{skill_id}",
            headers=self._headers,
            retry_strategy=self._retry_strategy,
        )

        if status_code >= 200 and status_code < 300:
            return response
        else:
            error_msg = f"Failed to get skill (status {status_code})"
            if isinstance(response, dict):
                error_detail = response.get("message", "")
                if error_detail:
                    error_msg += f": {error_detail}"
            raise Exception(error_msg)

    def create(
        self,
        name: str,
        description: str,
        package_path: Optional[str] = None,
        skill_type: str = "FUNCTIONS",
        mcp_server: Optional[dict] = None,
        usable_in: Optional[List[str]] = None,
        variables: Optional[dict[str, str]] = None,
        secrets: Optional[dict[str, str]] = None,
    ) -> str:
        """
        Create a new global skill on the Scout API.

        Args:
            name (str): The name of the skill
            description (str): A description of what the skill does
            package_path (Optional[str]): Local file path to the package zip file.
                Required for FUNCTIONS skills, must be omitted for MCP skills.
            skill_type (str): The type of skill ("FUNCTIONS" or "MCP", default: "FUNCTIONS")
            mcp_server (Optional[dict]): MCP server configuration including connection
                info (type, url/command, headers, environment) and the list of tools.
                Required for MCP skills.
            usable_in (Optional[List[str]]): Where the skill can be used (ASSISTANTS, CONVERSATIONS)
            variables (Optional[dict[str, str]]): Skill variables
            secrets (Optional[dict[str, str]]): Skill secrets

        Returns:
            str: The skill_id of the newly created skill

        Raises:
            Exception: If there is an error during the API request or file upload.
        """
        skill_data = self._build_skill_data(
            name=name,
            description=description,
            skill_type=skill_type,
            mcp_server=mcp_server,
            usable_in=usable_in,
            variables=variables,
            secrets=secrets,
        )

        post_kwargs: dict = {
            "url": f"{self._base_url}/api/skills/global",
            "headers": self._headers,
            "retry_strategy": self._retry_strategy,
        }

        if skill_type == "FUNCTIONS":
            if package_path is None:
                raise ValueError("package_path is required for FUNCTIONS skills")
            package_file = Path(package_path)
            if not package_file.exists():
                raise FileNotFoundError(f"Package file not found: {package_path}")
            with open(package_file, "rb") as f:
                post_kwargs["files"] = {
                    "file": (package_file.name, f, "application/zip")
                }
                post_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
                response, status_code = RequestUtils.post(**post_kwargs)
        else:
            post_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
            response, status_code = RequestUtils.post(**post_kwargs)

        if status_code >= 200 and status_code < 300:
            skill_id = response.get("skill_id", "")
            if not skill_id:
                raise Exception("Server did not return a skill_id")
            return skill_id
        else:
            error_msg = f"Failed to create skill (status {status_code})"
            if isinstance(response, dict):
                error_detail = response.get("message", "")
                if error_detail:
                    error_msg += f": {error_detail}"
            raise Exception(error_msg)

    def update(
        self,
        skill_id: str,
        name: str,
        description: str,
        package_path: Optional[str] = None,
        skill_type: str = "FUNCTIONS",
        mcp_server: Optional[dict] = None,
        usable_in: Optional[List[str]] = None,
        variables: Optional[dict[str, str]] = None,
        secrets: Optional[dict[str, Optional[str]]] = None,
    ) -> None:
        """
        Update an existing global skill on the Scout API.

        Args:
            skill_id (str): The ID of the skill to update
            name (str): The name of the skill
            description (str): A description of what the skill does
            package_path (Optional[str]): Local file path to the package zip file.
                Required for FUNCTIONS skills, must be omitted for MCP skills.
            skill_type (str): The type of skill ("FUNCTIONS" or "MCP", default: "FUNCTIONS")
            mcp_server (Optional[dict]): MCP server configuration including connection
                info (type, url/command, headers, environment) and the list of tools.
                Required for MCP skills.
            usable_in (Optional[List[str]]): Where the skill can be used (ASSISTANTS, CONVERSATIONS)
            variables (Optional[dict[str, str]]): Skill variables
            secrets (Optional[dict[str, Optional[str]]]): Skill secrets (None value preserves old)

        Raises:
            Exception: If there is an error during the API request or file upload.
        """
        skill_data = self._build_skill_data(
            name=name,
            description=description,
            skill_type=skill_type,
            mcp_server=mcp_server,
            usable_in=usable_in,
            variables=variables,
            secrets=secrets,
        )

        put_kwargs: dict = {
            "url": f"{self._base_url}/api/skills/global/{skill_id}",
            "headers": self._headers,
            "retry_strategy": self._retry_strategy,
        }

        if skill_type == "FUNCTIONS":
            if package_path is None:
                raise ValueError("package_path is required for FUNCTIONS skills")
            package_file = Path(package_path)
            if not package_file.exists():
                raise FileNotFoundError(f"Package file not found: {package_path}")
            with open(package_file, "rb") as f:
                put_kwargs["files"] = {
                    "file": (package_file.name, f, "application/zip")
                }
                put_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
                response, status_code = RequestUtils.put(**put_kwargs)
        else:
            put_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
            response, status_code = RequestUtils.put(**put_kwargs)

        if status_code < 200 or status_code >= 300:
            error_msg = f"Failed to update skill (status {status_code})"
            if isinstance(response, dict):
                error_detail = response.get("message", "")
                if error_detail:
                    error_msg += f": {error_detail}"
            raise Exception(error_msg)

    def set_catalog_metadata(
        self,
        skill_id: str,
        catalog_package_id: str,
        catalog_package_version: str,
    ) -> dict:
        """Set catalog metadata for a global skill.

        Args:
            skill_id: The ID of the global skill to update.
            catalog_package_id: The catalog package identifier (e.g. "org/package-name").
            catalog_package_version: The catalog package version string.

        Returns:
            The updated skill metadata as a dict.

        Raises:
            Exception: If the request fails (non-2xx status code).
        """
        response, status_code = RequestUtils.put(
            url=f"{self._base_url}/api/skills/global/{skill_id}/catalog-metadata",
            headers=self._headers,
            payload={
                "catalog_package_id": catalog_package_id,
                "catalog_package_version": catalog_package_version,
            },
            retry_strategy=self._retry_strategy,
        )
        if status_code < 200 or status_code >= 300:
            error_msg = f"Failed to set skill catalog metadata (status {status_code})"
            if isinstance(response, dict):
                error_detail = response.get("message", "")
                if error_detail:
                    error_msg += f": {error_detail}"
            raise Exception(error_msg)
        return response if isinstance(response, dict) else {}

    @staticmethod
    def _build_skill_data(
        name: str,
        description: str,
        skill_type: str,
        mcp_server: Optional[dict],
        usable_in: Optional[List[str]],
        variables: Optional[dict[str, str]],
        secrets: Optional[dict],
    ) -> dict:
        skill_data: dict = {
            "name": name,
            "description": description,
            "type": skill_type,
        }
        if skill_type == "MCP":
            if mcp_server is None:
                raise ValueError("mcp_server is required for MCP skills")
            skill_data["mcp_server"] = mcp_server
        if usable_in is not None:
            skill_data["usable_in"] = usable_in
        if variables is not None:
            skill_data["variables"] = variables
        if secrets is not None:
            skill_data["secrets"] = secrets
        return skill_data

    def upload_avatar(self, skill_id: str, file_path: str) -> dict:
        """
        Upload an avatar image for a specific skill.

        Args:
            skill_id (str): The ID of the skill to upload the avatar for.
            file_path (str): The local file path to the avatar image.

        Returns:
            dict: The response object containing information about the uploaded avatar.

        Raises:
            Exception: If there is an error during the file upload process.
        """
        with open(file_path, "rb") as f:
            content_type = mimetypes.guess_type(file_path)[0]
            files = {"file": (file_path, f, content_type)}

            response, status_code = RequestUtils.post(
                url=f"{self._base_url}/api/skills/global/{skill_id}/avatar/upload",
                headers=self._headers,
                files=files,
                retry_strategy=self._retry_strategy,
            )

        if status_code >= 200 and status_code < 300:
            return response
        else:
            error_msg = f"Failed to upload skill avatar (status {status_code})"
            if isinstance(response, dict):
                error_detail = response.get("message", "")
                if error_detail:
                    error_msg += f": {error_detail}"
            raise Exception(error_msg)

    def get_allowed_avatar_content_types(self) -> list[str]:
        """
        Get the list of allowed content types for skill avatars.

        Returns:
            list[str]: List of allowed MIME types for avatars.

        Raises:
            Exception: If there is an error during the API request.
        """
        response, status_code = RequestUtils.get(
            url=f"{self._base_url}/api/skills/avatar/upload/content-types",
            headers=self._headers,
            retry_strategy=self._retry_strategy,
        )

        if status_code >= 200 and status_code < 300:
            if isinstance(response, dict):
                return response.get("allowed_content_types", [])
            return []
        else:
            error_msg = (
                f"Failed to get allowed avatar content types (status {status_code})"
            )
            if isinstance(response, dict):
                error_detail = response.get("message", "")
                if error_detail:
                    error_msg += f": {error_detail}"
            raise Exception(error_msg)

    def build_skill(
        self,
        assistant_id: str,
        request: BuildSkillRequest,
    ) -> BuildSkillResponse:
        """
        Start building a skill asynchronously for an assistant using the AI skills builder.

        This method initiates an async job that uses an LLM to generate a skill based on
        the provided instructions. Use the returned `run_protected_url` to poll for completion.

        Args:
            assistant_id (str): The ID of the assistant to build the skill for.
            request (BuildSkillRequest): The build skill request containing instructions and options.

        Returns:
            BuildSkillResponse: Contains `run_protected_url` and `function_progress_url` for tracking.

        Example:
            from forratypes.skills import BuildSkillRequest

            request = BuildSkillRequest(
                instructions="Create a function that returns Hello World"
            )
            response = api.skills.build_skill(assistant_id="...", request=request)
            # Poll response.run_protected_url to check completion status
        """
        response, status_code = RequestUtils.post(
            url=f"{self._base_url}/api/skills/assistants/{assistant_id}/build",
            headers=self._headers,
            json_payload=request.model_dump(exclude_none=True),
            retry_strategy=self._retry_strategy,
        )

        return BuildSkillResponse.model_validate(response)

build_skill(assistant_id, request)

Start building a skill asynchronously for an assistant using the AI skills builder.

This method initiates an async job that uses an LLM to generate a skill based on the provided instructions. Use the returned run_protected_url to poll for completion.

Parameters:

Name Type Description Default
assistant_id str

The ID of the assistant to build the skill for.

required
request BuildSkillRequest

The build skill request containing instructions and options.

required

Returns:

Name Type Description
BuildSkillResponse BuildSkillResponse

Contains run_protected_url and function_progress_url for tracking.

Example

from forratypes.skills import BuildSkillRequest

request = BuildSkillRequest( instructions="Create a function that returns Hello World" ) response = api.skills.build_skill(assistant_id="...", request=request)

Poll response.run_protected_url to check completion status

Source code in forrasdk/api/skills.py
def build_skill(
    self,
    assistant_id: str,
    request: BuildSkillRequest,
) -> BuildSkillResponse:
    """
    Start building a skill asynchronously for an assistant using the AI skills builder.

    This method initiates an async job that uses an LLM to generate a skill based on
    the provided instructions. Use the returned `run_protected_url` to poll for completion.

    Args:
        assistant_id (str): The ID of the assistant to build the skill for.
        request (BuildSkillRequest): The build skill request containing instructions and options.

    Returns:
        BuildSkillResponse: Contains `run_protected_url` and `function_progress_url` for tracking.

    Example:
        from forratypes.skills import BuildSkillRequest

        request = BuildSkillRequest(
            instructions="Create a function that returns Hello World"
        )
        response = api.skills.build_skill(assistant_id="...", request=request)
        # Poll response.run_protected_url to check completion status
    """
    response, status_code = RequestUtils.post(
        url=f"{self._base_url}/api/skills/assistants/{assistant_id}/build",
        headers=self._headers,
        json_payload=request.model_dump(exclude_none=True),
        retry_strategy=self._retry_strategy,
    )

    return BuildSkillResponse.model_validate(response)

create(name, description, package_path=None, skill_type='FUNCTIONS', mcp_server=None, usable_in=None, variables=None, secrets=None)

Create a new global skill on the Scout API.

Parameters:

Name Type Description Default
name str

The name of the skill

required
description str

A description of what the skill does

required
package_path Optional[str]

Local file path to the package zip file. Required for FUNCTIONS skills, must be omitted for MCP skills.

None
skill_type str

The type of skill ("FUNCTIONS" or "MCP", default: "FUNCTIONS")

'FUNCTIONS'
mcp_server Optional[dict]

MCP server configuration including connection info (type, url/command, headers, environment) and the list of tools. Required for MCP skills.

None
usable_in Optional[List[str]]

Where the skill can be used (ASSISTANTS, CONVERSATIONS)

None
variables Optional[dict[str, str]]

Skill variables

None
secrets Optional[dict[str, str]]

Skill secrets

None

Returns:

Name Type Description
str str

The skill_id of the newly created skill

Raises:

Type Description
Exception

If there is an error during the API request or file upload.

Source code in forrasdk/api/skills.py
def create(
    self,
    name: str,
    description: str,
    package_path: Optional[str] = None,
    skill_type: str = "FUNCTIONS",
    mcp_server: Optional[dict] = None,
    usable_in: Optional[List[str]] = None,
    variables: Optional[dict[str, str]] = None,
    secrets: Optional[dict[str, str]] = None,
) -> str:
    """
    Create a new global skill on the Scout API.

    Args:
        name (str): The name of the skill
        description (str): A description of what the skill does
        package_path (Optional[str]): Local file path to the package zip file.
            Required for FUNCTIONS skills, must be omitted for MCP skills.
        skill_type (str): The type of skill ("FUNCTIONS" or "MCP", default: "FUNCTIONS")
        mcp_server (Optional[dict]): MCP server configuration including connection
            info (type, url/command, headers, environment) and the list of tools.
            Required for MCP skills.
        usable_in (Optional[List[str]]): Where the skill can be used (ASSISTANTS, CONVERSATIONS)
        variables (Optional[dict[str, str]]): Skill variables
        secrets (Optional[dict[str, str]]): Skill secrets

    Returns:
        str: The skill_id of the newly created skill

    Raises:
        Exception: If there is an error during the API request or file upload.
    """
    skill_data = self._build_skill_data(
        name=name,
        description=description,
        skill_type=skill_type,
        mcp_server=mcp_server,
        usable_in=usable_in,
        variables=variables,
        secrets=secrets,
    )

    post_kwargs: dict = {
        "url": f"{self._base_url}/api/skills/global",
        "headers": self._headers,
        "retry_strategy": self._retry_strategy,
    }

    if skill_type == "FUNCTIONS":
        if package_path is None:
            raise ValueError("package_path is required for FUNCTIONS skills")
        package_file = Path(package_path)
        if not package_file.exists():
            raise FileNotFoundError(f"Package file not found: {package_path}")
        with open(package_file, "rb") as f:
            post_kwargs["files"] = {
                "file": (package_file.name, f, "application/zip")
            }
            post_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
            response, status_code = RequestUtils.post(**post_kwargs)
    else:
        post_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
        response, status_code = RequestUtils.post(**post_kwargs)

    if status_code >= 200 and status_code < 300:
        skill_id = response.get("skill_id", "")
        if not skill_id:
            raise Exception("Server did not return a skill_id")
        return skill_id
    else:
        error_msg = f"Failed to create skill (status {status_code})"
        if isinstance(response, dict):
            error_detail = response.get("message", "")
            if error_detail:
                error_msg += f": {error_detail}"
        raise Exception(error_msg)

get(skill_id)

Get a specific global skill by ID.

Parameters:

Name Type Description Default
skill_id str

The ID of the skill to retrieve

required

Returns:

Name Type Description
dict dict

Skill dictionary containing id, name, description, type, functions_status, etc.

Raises:

Type Description
Exception

If there is an error during the API request or skill not found.

Source code in forrasdk/api/skills.py
def get(self, skill_id: str) -> dict:
    """
    Get a specific global skill by ID.

    Args:
        skill_id (str): The ID of the skill to retrieve

    Returns:
        dict: Skill dictionary containing id, name, description, type, functions_status, etc.

    Raises:
        Exception: If there is an error during the API request or skill not found.
    """
    response, status_code = RequestUtils.get(
        url=f"{self._base_url}/api/skills/global/{skill_id}",
        headers=self._headers,
        retry_strategy=self._retry_strategy,
    )

    if status_code >= 200 and status_code < 300:
        return response
    else:
        error_msg = f"Failed to get skill (status {status_code})"
        if isinstance(response, dict):
            error_detail = response.get("message", "")
            if error_detail:
                error_msg += f": {error_detail}"
        raise Exception(error_msg)

get_allowed_avatar_content_types()

Get the list of allowed content types for skill avatars.

Returns:

Type Description
list[str]

list[str]: List of allowed MIME types for avatars.

Raises:

Type Description
Exception

If there is an error during the API request.

Source code in forrasdk/api/skills.py
def get_allowed_avatar_content_types(self) -> list[str]:
    """
    Get the list of allowed content types for skill avatars.

    Returns:
        list[str]: List of allowed MIME types for avatars.

    Raises:
        Exception: If there is an error during the API request.
    """
    response, status_code = RequestUtils.get(
        url=f"{self._base_url}/api/skills/avatar/upload/content-types",
        headers=self._headers,
        retry_strategy=self._retry_strategy,
    )

    if status_code >= 200 and status_code < 300:
        if isinstance(response, dict):
            return response.get("allowed_content_types", [])
        return []
    else:
        error_msg = (
            f"Failed to get allowed avatar content types (status {status_code})"
        )
        if isinstance(response, dict):
            error_detail = response.get("message", "")
            if error_detail:
                error_msg += f": {error_detail}"
        raise Exception(error_msg)

list_all(search=None, order_by=None)

List all global skills from the Scout API.

Parameters:

Name Type Description Default
search Optional[str]

Optional search query to filter skills

None
order_by Optional[str]

Optional ordering parameter

None

Returns:

Type Description
list[dict]

list[dict]: List of skill dictionaries containing id, name, description, type, functions_status, etc.

Raises:

Type Description
Exception

If there is an error during the API request.

Source code in forrasdk/api/skills.py
def list_all(
    self, search: Optional[str] = None, order_by: Optional[str] = None
) -> list[dict]:
    """
    List all global skills from the Scout API.

    Args:
        search (Optional[str]): Optional search query to filter skills
        order_by (Optional[str]): Optional ordering parameter

    Returns:
        list[dict]: List of skill dictionaries containing id, name, description, type, functions_status, etc.

    Raises:
        Exception: If there is an error during the API request.
    """
    params = {}
    if search:
        params["search"] = search
    if order_by:
        params["orderBy"] = order_by

    response, status_code = RequestUtils.get(
        url=f"{self._base_url}/api/skills/global",
        headers=self._headers,
        params=params if params else None,
        retry_strategy=self._retry_strategy,
    )

    if status_code >= 200 and status_code < 300:
        return response if isinstance(response, list) else []
    else:
        error_msg = f"Failed to list skills (status {status_code})"
        if isinstance(response, dict):
            error_detail = response.get("message", "")
            if error_detail:
                error_msg += f": {error_detail}"
        raise Exception(error_msg)

set_catalog_metadata(skill_id, catalog_package_id, catalog_package_version)

Set catalog metadata for a global skill.

Parameters:

Name Type Description Default
skill_id str

The ID of the global skill to update.

required
catalog_package_id str

The catalog package identifier (e.g. "org/package-name").

required
catalog_package_version str

The catalog package version string.

required

Returns:

Type Description
dict

The updated skill metadata as a dict.

Raises:

Type Description
Exception

If the request fails (non-2xx status code).

Source code in forrasdk/api/skills.py
def set_catalog_metadata(
    self,
    skill_id: str,
    catalog_package_id: str,
    catalog_package_version: str,
) -> dict:
    """Set catalog metadata for a global skill.

    Args:
        skill_id: The ID of the global skill to update.
        catalog_package_id: The catalog package identifier (e.g. "org/package-name").
        catalog_package_version: The catalog package version string.

    Returns:
        The updated skill metadata as a dict.

    Raises:
        Exception: If the request fails (non-2xx status code).
    """
    response, status_code = RequestUtils.put(
        url=f"{self._base_url}/api/skills/global/{skill_id}/catalog-metadata",
        headers=self._headers,
        payload={
            "catalog_package_id": catalog_package_id,
            "catalog_package_version": catalog_package_version,
        },
        retry_strategy=self._retry_strategy,
    )
    if status_code < 200 or status_code >= 300:
        error_msg = f"Failed to set skill catalog metadata (status {status_code})"
        if isinstance(response, dict):
            error_detail = response.get("message", "")
            if error_detail:
                error_msg += f": {error_detail}"
        raise Exception(error_msg)
    return response if isinstance(response, dict) else {}

update(skill_id, name, description, package_path=None, skill_type='FUNCTIONS', mcp_server=None, usable_in=None, variables=None, secrets=None)

Update an existing global skill on the Scout API.

Parameters:

Name Type Description Default
skill_id str

The ID of the skill to update

required
name str

The name of the skill

required
description str

A description of what the skill does

required
package_path Optional[str]

Local file path to the package zip file. Required for FUNCTIONS skills, must be omitted for MCP skills.

None
skill_type str

The type of skill ("FUNCTIONS" or "MCP", default: "FUNCTIONS")

'FUNCTIONS'
mcp_server Optional[dict]

MCP server configuration including connection info (type, url/command, headers, environment) and the list of tools. Required for MCP skills.

None
usable_in Optional[List[str]]

Where the skill can be used (ASSISTANTS, CONVERSATIONS)

None
variables Optional[dict[str, str]]

Skill variables

None
secrets Optional[dict[str, Optional[str]]]

Skill secrets (None value preserves old)

None

Raises:

Type Description
Exception

If there is an error during the API request or file upload.

Source code in forrasdk/api/skills.py
def update(
    self,
    skill_id: str,
    name: str,
    description: str,
    package_path: Optional[str] = None,
    skill_type: str = "FUNCTIONS",
    mcp_server: Optional[dict] = None,
    usable_in: Optional[List[str]] = None,
    variables: Optional[dict[str, str]] = None,
    secrets: Optional[dict[str, Optional[str]]] = None,
) -> None:
    """
    Update an existing global skill on the Scout API.

    Args:
        skill_id (str): The ID of the skill to update
        name (str): The name of the skill
        description (str): A description of what the skill does
        package_path (Optional[str]): Local file path to the package zip file.
            Required for FUNCTIONS skills, must be omitted for MCP skills.
        skill_type (str): The type of skill ("FUNCTIONS" or "MCP", default: "FUNCTIONS")
        mcp_server (Optional[dict]): MCP server configuration including connection
            info (type, url/command, headers, environment) and the list of tools.
            Required for MCP skills.
        usable_in (Optional[List[str]]): Where the skill can be used (ASSISTANTS, CONVERSATIONS)
        variables (Optional[dict[str, str]]): Skill variables
        secrets (Optional[dict[str, Optional[str]]]): Skill secrets (None value preserves old)

    Raises:
        Exception: If there is an error during the API request or file upload.
    """
    skill_data = self._build_skill_data(
        name=name,
        description=description,
        skill_type=skill_type,
        mcp_server=mcp_server,
        usable_in=usable_in,
        variables=variables,
        secrets=secrets,
    )

    put_kwargs: dict = {
        "url": f"{self._base_url}/api/skills/global/{skill_id}",
        "headers": self._headers,
        "retry_strategy": self._retry_strategy,
    }

    if skill_type == "FUNCTIONS":
        if package_path is None:
            raise ValueError("package_path is required for FUNCTIONS skills")
        package_file = Path(package_path)
        if not package_file.exists():
            raise FileNotFoundError(f"Package file not found: {package_path}")
        with open(package_file, "rb") as f:
            put_kwargs["files"] = {
                "file": (package_file.name, f, "application/zip")
            }
            put_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
            response, status_code = RequestUtils.put(**put_kwargs)
    else:
        put_kwargs["data"] = {"skill_data": json.dumps(skill_data)}
        response, status_code = RequestUtils.put(**put_kwargs)

    if status_code < 200 or status_code >= 300:
        error_msg = f"Failed to update skill (status {status_code})"
        if isinstance(response, dict):
            error_detail = response.get("message", "")
            if error_detail:
                error_msg += f": {error_detail}"
        raise Exception(error_msg)

upload_avatar(skill_id, file_path)

Upload an avatar image for a specific skill.

Parameters:

Name Type Description Default
skill_id str

The ID of the skill to upload the avatar for.

required
file_path str

The local file path to the avatar image.

required

Returns:

Name Type Description
dict dict

The response object containing information about the uploaded avatar.

Raises:

Type Description
Exception

If there is an error during the file upload process.

Source code in forrasdk/api/skills.py
def upload_avatar(self, skill_id: str, file_path: str) -> dict:
    """
    Upload an avatar image for a specific skill.

    Args:
        skill_id (str): The ID of the skill to upload the avatar for.
        file_path (str): The local file path to the avatar image.

    Returns:
        dict: The response object containing information about the uploaded avatar.

    Raises:
        Exception: If there is an error during the file upload process.
    """
    with open(file_path, "rb") as f:
        content_type = mimetypes.guess_type(file_path)[0]
        files = {"file": (file_path, f, content_type)}

        response, status_code = RequestUtils.post(
            url=f"{self._base_url}/api/skills/global/{skill_id}/avatar/upload",
            headers=self._headers,
            files=files,
            retry_strategy=self._retry_strategy,
        )

    if status_code >= 200 and status_code < 300:
        return response
    else:
        error_msg = f"Failed to upload skill avatar (status {status_code})"
        if isinstance(response, dict):
            error_detail = response.get("message", "")
            if error_detail:
                error_msg += f": {error_detail}"
        raise Exception(error_msg)