> ## Documentation Index
> Fetch the complete documentation index at: https://innateinc-theo-docs-skills-authoring-api.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# External Services & APIs

export const ServiceStateTypesTable = () => {
  const rows = [{
    stateType: "image: MainImage",
    description: "Latest camera frame (base64 JPEG)."
  }, {
    stateType: "odom: Odometry",
    description: "Current 2D pose and velocities."
  }, {
    stateType: "map: Map",
    description: "Occupancy grid."
  }, {
    stateType: "head_position: HeadState",
    description: "Head tilt angle."
  }];
  return <div className="interface-methods-table-wrap">
      <table className="interface-methods-table">
        <thead>
          <tr>
            <th>Declaration</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. The guidelines are
written out explicitly here — the agent-facing text carries usage policy ("emergency
only") that goes beyond a one-line description:

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
from innate import Skill, SkillReturn

class SendEmail(Skill):
    def guidelines(self) -> str:
        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 = None) -> SkillReturn:
        ...  # Send via SMTP; return a success message or self.fail(...)
```

The skill name defaults to the snake\_cased class name, so `SendEmail` is callable as `send_email` — no `name` property needed.

**Parameters:**

<SendEmailParametersTable />

### SendPictureViaEmail

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

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
from innate import MainImage, Skill, SkillReturn

class SendPictureViaEmail(Skill):
    # Declared camera frame — guaranteed before execute() starts,
    # updated at 50Hz while the skill runs
    image: MainImage

    def execute(self, subject: str, message: str, recipient: str | None = None) -> SkillReturn:
        jpeg_bytes = self.image.jpeg
        # Attach image and send
```

This skill demonstrates **state dependencies** — declaring robot state with a type
annotation. Because `image` is declared without `| None`, the run fails up front if no
frame arrives, so `execute()` never needs a guard.

### RetrieveEmails

Fetches recent emails from configured account.

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
class RetrieveEmails(Skill):
    def guidelines(self) -> str:
        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) -> SkillReturn:
        ...  # Connect to IMAP, fetch emails, return the formatted 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

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
import os
from innate import Skill, SkillReturn

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")

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

    def execute(self, param: str) -> SkillReturn:
        try:
            result = self._call_service(param)
        except TimeoutError:
            self.fail("Service timed out")
        except Exception as e:
            self.fail(f"Error: {e}")
        return f"Success: {result}"
```

### 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, then list it in an agent with `from custom_skills.retrieve_emails import RetrieveEmails`:

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
import email
import imaplib
from innate import Skill, SkillReturn

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"

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

    def execute(self, count: int = 5) -> SkillReturn:
        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.feedback(email_data)
            return f"Retrieved {count} emails with subjects and content"
        except Exception as e:
            self.fail(f"Failed to retrieve emails: {e}")
```

### Best Practices

**Authentication**

* Store credentials in environment variables or secret managers

* Never hardcode passwords or API keys

* Validate credentials at initialization

**Error Handling**

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
def execute(self, query: str) -> SkillReturn:
    try:
        response = self.client.call(query, timeout=10)
    except RateLimitError:
        self.fail("Rate limit exceeded")
    except NetworkError:
        self.fail("Network unavailable")
    except Exception as e:
        self.fail(f"Unexpected error: {e}")
    return f"Result: {response}"
```

**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 with class-level type annotations:

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
from innate import MainImage, Odometry, Skill

class MySkill(Skill):
    # Declared state — injected and updated at 50Hz while the skill runs
    image: MainImage
    odom: Odometry

    def execute(self):
        # Both are guaranteed here; append `| None` to a declaration to
        # make it best-effort instead (then guard your reads).
        ...
```

Available state types:

<ServiceStateTypesTable />

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

## Cancellation

Many service operations are atomic and cannot be meaningfully cancelled. A skill that never blocks on a framework call — no `self.sleep()`, no sub-skill — simply runs to completion, which is usually the right behavior for a network request. Mention it in the guidelines if the agent should know:

```innatepy theme={"languages":{"custom":["/languages/python-typed.json"]}}
def guidelines(self) -> str:
    return "Sends the email immediately; cannot be cancelled once started."
```

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