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

# External Services & APIs

export const ServiceStateTypesTable = () => {
  const rows = [{
    stateType: "LAST_MAIN_CAMERA_IMAGE_B64",
    description: "Latest camera frame (base64 JPEG)."
  }, {
    stateType: "LAST_ODOM",
    description: "Current odometry."
  }, {
    stateType: "LAST_MAP",
    description: "Occupancy grid."
  }, {
    stateType: "LAST_HEAD_POSITION",
    description: "Head tilt angle."
  }];
  return <div className="interface-methods-table-wrap">
      <table className="interface-methods-table">
        <thead>
          <tr>
            <th>State Type</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(row => <tr key={row.stateType}>
              <td>
                <span className="interface-param-badge">{row.stateType}</span>
              </td>
              <td>{row.description}</td>
            </tr>)}
        </tbody>
      </table>
    </div>;
};

export const SendEmailParametersTable = () => {
  const rows = [{
    parameter: "subject",
    type: "str",
    required: "Yes",
    description: "Email subject line."
  }, {
    parameter: "message",
    type: "str",
    required: "Yes",
    description: "Email body content."
  }, {
    parameter: "recipients",
    type: "list[str]",
    required: "No",
    description: "Recipients (defaults to configured list)."
  }];
  return <div className="interface-methods-table-wrap">
      <table className="interface-methods-table">
        <thead>
          <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Required</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(row => <tr key={row.parameter}>
              <td>
                <span className="interface-method-pill">{row.parameter}</span>
              </td>
              <td>
                <span className="interface-param-badge">{row.type}</span>
              </td>
              <td>
                <span className="interface-param-badge">{row.required}</span>
              </td>
              <td>{row.description}</td>
            </tr>)}
        </tbody>
      </table>
    </div>;
};

Some skills reach beyond the robot's body — sending emails, calling APIs, retrieving
information. These are ordinary [code-defined skills](/software/skills/code-defined-skills)
that happen to talk to the network: they implement explicit protocols and handle
authentication, errors, and connectivity.

## What to keep in mind

Talking to an external service is a more deterministic domain than acting in the physical
world, but it comes with its own constraints:

* **Protocol-based**: Follow defined APIs and standards

* **Atomic**: Many operations cannot be cancelled once started

* **Reliable**: Once working, behavior is consistent

* **Network-dependent**: Must handle connectivity issues

## Built-in examples

### SendEmail

Sends email notifications, typically for alerts or status updates.

```python theme={null}
class SendEmail(Skill):
    @property
    def name(self):
        return "send_email"

    def guidelines(self):
        return (
            "Use to send an emergency email notification. Provide a subject and "
            "message. You can optionally provide a list of recipients, otherwise "
            "it will be sent to the default list. This should be used when a "
            "potential emergency is detected and assistance might be required."
        )

    def execute(self, subject: str, message: str, recipients: list[str] = None):
        # Send via SMTP
        # Returns (message, SkillResult)
```

**Parameters:**

<SendEmailParametersTable />

### SendPictureViaEmail

Sends an email with the robot's current camera view attached.

```python theme={null}
class SendPictureViaEmail(Skill):
    # Declare camera image dependency - updated at 50Hz while skill runs
    image = RobotState(RobotStateType.LAST_MAIN_CAMERA_IMAGE_B64)

    def execute(self, subject: str, message: str, recipient: str = None):
        if not self.image:
            return "No image available to send", SkillResult.FAILURE
        # Attach image and send
```

This skill demonstrates **state dependencies**—using the `RobotState` descriptor to declare required robot state that the system updates at 50Hz.

### RetrieveEmails

Fetches recent emails from configured account.

```python theme={null}
class RetrieveEmails(Skill):
    def guidelines(self):
        return (
            "Use to retrieve recent emails from the configured email account. "
            "Provide the number of emails to retrieve (default is 5). "
            "Returns email subjects and content."
        )

    def execute(self, count: int = 5):
        # Connect to IMAP, fetch emails
        # Returns formatted email list
```

Want to build your own version against your own account? There's a complete implementation in the [worked example](#worked-example-retrieveemails) below.

## Building a service skill

### Template

```python theme={null}
import os
from brain_client.skill_types import Skill, SkillResult

class MyServiceSkill(Skill):
    def __init__(self, logger):
        super().__init__(logger)
        self.api_key = os.environ.get("SERVICE_API_KEY")
        if not self.api_key:
            raise ValueError("SERVICE_API_KEY not configured")

    @property
    def name(self):
        return "my_service_skill"

    def guidelines(self):
        return "Use when [describe appropriate use cases]"

    def execute(self, param: str):
        try:
            # Call external service
            result = self._call_service(param)
            return f"Success: {result}", SkillResult.SUCCESS
        except TimeoutError:
            return "Service timed out", SkillResult.FAILURE
        except Exception as e:
            return f"Error: {e}", SkillResult.FAILURE

    def cancel(self):
        return "Operation cannot be cancelled"
```

### Worked example: RetrieveEmails

Here's a complete, runnable custom skill that fetches your latest Gmail messages over IMAP. Save it as `~/innate-os/workspace/custom_skills/retrieve_emails.py` on the robot, and reference it from an agent as `local/retrieve_emails` (custom skills are namespaced `local/`, shipped skills `innate-os/`):

```python theme={null}
import imaplib
import email
from brain_client.skill_types import Skill, SkillResult

class RetrieveEmails(Skill):
    def __init__(self, logger):
        super().__init__(logger)
        self.imap_server = "imap.gmail.com"
        self.email = "your_email@gmail.com"
        # Use a Gmail App Password (https://myaccount.google.com/apppasswords),
        # not your main account password.
        self.password = "your_app_password"

    @property
    def name(self):
        return "retrieve_emails"

    def guidelines(self):
        return "Use to retrieve recent emails. Provide count (default 5). Returns subjects and content."

    def execute(self, count: int = 5):
        count = min(max(1, count), 20)
        try:
            mail = imaplib.IMAP4_SSL(self.imap_server, 993)
            mail.login(self.email, self.password)
            # ... fetch and process emails ...
            email_data = "Email 1: Subject, From, Content..."
            self._send_feedback(email_data)
            return f"Retrieved {count} emails with subjects and content", SkillResult.SUCCESS
        except Exception as e:
            return f"Failed to retrieve emails: {str(e)}", SkillResult.FAILURE

    def cancel(self):
        # Every skill must implement cancel(); nothing long-running to
        # interrupt here, so just report it.
        return "Email retrieval cancelled"
```

### Best Practices

**Authentication**

* Store credentials in environment variables or secret managers

* Never hardcode passwords or API keys

* Validate credentials at initialization

**Error Handling**

```python theme={null}
def execute(self, query: str):
    try:
        response = self.client.call(query, timeout=10)
        return f"Result: {response}", SkillResult.SUCCESS
    except RateLimitError:
        return "Rate limit exceeded", SkillResult.FAILURE
    except NetworkError:
        return "Network unavailable", SkillResult.FAILURE
    except Exception as e:
        return f"Unexpected error: {e}", SkillResult.FAILURE
```

**Timeouts**

* Always set explicit timeouts on network calls

* Prevent blocking indefinitely on slow services

**Idempotency**

* Design operations to be safely retryable when possible

* Consider partial failure scenarios

## Requesting Robot State

Skills declare sensor data dependencies using the `RobotState` descriptor:

```python theme={null}
from brain_client.skill_types import RobotState, RobotStateType

class MySkill(Skill):
    # Declare state dependencies at class level
    image = RobotState(RobotStateType.LAST_MAIN_CAMERA_IMAGE_B64)
    odom = RobotState(RobotStateType.LAST_ODOM)

    def execute(self):
        if self.image:
            # Use the latest camera frame
            pass
```

The system automatically updates declared state at 50Hz while your skill runs. Always check for `None` on first access.

Available state types:

<ServiceStateTypesTable />

See [Robot State](/software/skills/code-defined-skills/robot-state) for more details on the RobotState system.

## Cancellation

Many service operations are atomic and cannot be meaningfully cancelled:

```python theme={null}
def cancel(self):
    return "Email sending is an atomic operation that cannot be canceled once started"
```

The Innate agent understands this limitation and factors it into planning decisions.
