# Plugin Scripting API Reference

This document describes the Safe Scripting API available to plugins in SmoothSchedule. All methods are accessed through the `api` object provided to plugin scripts.

## Table of Contents

- [Overview](#overview)
- [Rate Limits](#rate-limits)
- [Feature Requirements](#feature-requirements)
- [API Methods](#api-methods)
  - [Appointments](#appointments)
  - [Customers](#customers)
  - [Resources](#resources)
  - [Services](#services)
  - [Locations](#locations)
  - [Staff](#staff)
  - [Payments & Invoices](#payments--invoices)
  - [Contracts](#contracts)
  - [SMS](#sms)
  - [Email](#email)
  - [Video Conferencing](#video-conferencing)
  - [Analytics](#analytics)
  - [HTTP Requests](#http-requests)
  - [Utilities](#utilities)

---

## Overview

The Plugin Scripting API provides safe, sandboxed access to business data and operations. Scripts run in a restricted environment that prevents:

- File system access
- Arbitrary network access (only whitelisted URLs)
- Infinite loops (max 10,000 iterations)
- Excessive execution time (max 30 seconds)
- Memory abuse (max 50MB)

## Rate Limits

- **Max API calls per execution:** 50
- **Max execution time:** 30 seconds
- **Max loop iterations:** 10,000
- **Max output size:** 10,000 characters

## Feature Requirements

Some API methods require specific plan features. If a feature is not available, the method will raise a `ScriptExecutionError`.

| Feature Code | Required For |
|--------------|--------------|
| `sms_enabled` | SMS methods |
| `payment_processing` | Payment/Invoice methods |
| `can_use_contracts` | Contract methods |
| `multi_location` | Location stats |
| `can_add_video_conferencing` | Video meeting creation |
| `can_use_email_templates` | Email template methods |
| `advanced_reporting` | Analytics methods |
| `recurring_appointments` | Recurring appointment methods |

---

## API Methods

### Appointments

#### `api.get_appointments(**filters)`

Get appointments for this business.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status: `SCHEDULED`, `COMPLETED`, `CANCELED`, `NO_SHOW` |
| `start_date` | string | Start date filter (YYYY-MM-DD) |
| `end_date` | string | End date filter (YYYY-MM-DD) |
| `limit` | int | Max results (default: 100, max: 1000) |

**Returns:**
```json
[
  {
    "id": 1234,
    "title": "Haircut with John",
    "start_time": "2025-01-15T10:00:00+00:00",
    "end_time": "2025-01-15T11:00:00+00:00",
    "status": "SCHEDULED",
    "notes": "First time customer, prefers short style"
  },
  {
    "id": 1235,
    "title": "Color Treatment",
    "start_time": "2025-01-15T14:00:00+00:00",
    "end_time": "2025-01-15T16:00:00+00:00",
    "status": "COMPLETED",
    "notes": ""
  }
]
```

**Example:**
```python
# Get all scheduled appointments for next week
appointments = api.get_appointments(
    status='SCHEDULED',
    start_date='2025-01-15',
    end_date='2025-01-22',
    limit=50
)

for apt in appointments:
    api.log(f"Appointment: {apt['title']} at {apt['start_time']}")
```

---

#### `api.create_appointment(title, start_time, end_time, **kwargs)`

Create a new appointment.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `title` | string | Appointment title (required) |
| `start_time` | string | Start datetime in ISO format (required) |
| `end_time` | string | End datetime in ISO format (required) |
| `notes` | string | Optional notes |

**Returns:**
```json
{
  "id": 1236,
  "title": "New Consultation",
  "start_time": "2025-01-20T09:00:00+00:00",
  "end_time": "2025-01-20T10:00:00+00:00"
}
```

---

#### `api.update_appointment(appointment_id, **updates)`

Update an existing appointment.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `appointment_id` | int | ID of appointment to update (required) |
| `title` | string | New title |
| `notes` | string | New notes |
| `status` | string | New status |
| `start_time` | string | New start time (ISO format) |
| `end_time` | string | New end time (ISO format) |

**Returns:**
```json
{
  "id": 1234,
  "title": "Updated Title",
  "start_time": "2025-01-15T10:00:00+00:00",
  "end_time": "2025-01-15T11:00:00+00:00",
  "status": "COMPLETED",
  "notes": "Customer was satisfied"
}
```

---

#### `api.get_recurring_appointments(**filters)`

Get recurring appointment series. **Requires:** `recurring_appointments` feature.

**Returns:**
```json
[
  {
    "id": 500,
    "title": "Weekly Team Meeting",
    "recurring_pattern": "RRULE:FREQ=WEEKLY;BYDAY=MO",
    "start_time": "2025-01-06T09:00:00+00:00",
    "status": "SCHEDULED"
  }
]
```

---

### Customers

#### `api.get_customers(**filters)`

Get customers for this business.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `has_email` | bool | Filter to customers with email |
| `limit` | int | Max results (default: 100, max: 1000) |

**Returns:**
```json
[
  {
    "id": 5001,
    "email": "jane.doe@example.com",
    "name": "Jane Doe",
    "phone": "+15551234567"
  },
  {
    "id": 5002,
    "email": "john.smith@example.com",
    "name": "John Smith",
    "phone": "+15559876543"
  }
]
```

**Example:**
```python
# Send reminder to all customers with email
customers = api.get_customers(has_email=True)
for customer in customers:
    api.send_email(
        customer['email'],
        "We miss you!",
        f"Hi {customer['name']}, it's been a while. Book your next appointment today!"
    )
```

---

### Resources

#### `api.get_resources(**filters)`

Get resources (staff, rooms, equipment) for this business.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `type` | string | Filter by type: `STAFF`, `ROOM`, `EQUIPMENT` |
| `is_active` | bool | Filter by active status (default: True) |
| `location_id` | int | Filter by location ID |
| `limit` | int | Max results (default: 100, max: 500) |

**Returns:**
```json
[
  {
    "id": 101,
    "name": "Sarah Johnson",
    "type": "STAFF",
    "resource_type_name": "Hair Stylist",
    "description": "Senior stylist specializing in color",
    "is_active": true,
    "max_concurrent_events": 1,
    "location_id": 1,
    "location_name": "Downtown Salon",
    "is_mobile": false,
    "user_id": 201,
    "user_name": "Sarah Johnson",
    "user_email": "sarah@salon.com"
  },
  {
    "id": 102,
    "name": "Treatment Room A",
    "type": "ROOM",
    "resource_type_name": "Treatment Room",
    "description": "Private room with massage table",
    "is_active": true,
    "max_concurrent_events": 1,
    "location_id": 1,
    "location_name": "Downtown Salon",
    "is_mobile": false,
    "user_id": null,
    "user_name": null,
    "user_email": null
  }
]
```

---

#### `api.get_resource_availability(resource_id, start_date=None, end_date=None, days=7)`

Get availability information for a specific resource.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `resource_id` | int | ID of the resource (required) |
| `start_date` | string | Start date (YYYY-MM-DD), defaults to today |
| `end_date` | string | End date (YYYY-MM-DD) |
| `days` | int | Days to check (default: 7, max: 30) |

**Returns:**
```json
{
  "resource_id": 101,
  "resource_name": "Sarah Johnson",
  "period_start": "2025-01-15T00:00:00+00:00",
  "period_end": "2025-01-22T00:00:00+00:00",
  "total_hours": 56,
  "booked_hours": 32.5,
  "available_hours": 23.5,
  "utilization": 58.0,
  "appointment_count": 15,
  "appointments": [
    {
      "id": 1234,
      "title": "Haircut",
      "start_time": "2025-01-15T10:00:00+00:00",
      "end_time": "2025-01-15T11:00:00+00:00",
      "status": "SCHEDULED"
    }
  ]
}
```

**Example:**
```python
# Alert manager if staff utilization is low
resources = api.get_resources(type='STAFF')
for resource in resources:
    availability = api.get_resource_availability(resource['id'], days=7)
    if availability['utilization'] < 50:
        api.send_email(
            'manager@salon.com',
            f"Low Utilization Alert: {resource['name']}",
            f"{resource['name']} is only {availability['utilization']}% booked this week."
        )
```

---

### Services

#### `api.get_services(**filters)`

Get services offered by this business.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `is_active` | bool | Filter by active status (default: True) |
| `location_id` | int | Filter by location ID |
| `limit` | int | Max results (default: 100, max: 500) |

**Returns:**
```json
[
  {
    "id": 301,
    "name": "Haircut",
    "description": "Professional haircut and styling",
    "duration": 60,
    "price": 45.00,
    "price_cents": 4500,
    "is_active": true,
    "variable_pricing": false,
    "requires_deposit": true,
    "deposit_amount_cents": 1000,
    "deposit_percent": null,
    "prep_time": 5,
    "takedown_time": 10,
    "is_global": true
  },
  {
    "id": 302,
    "name": "Color Treatment",
    "description": "Full color with consultation",
    "duration": 120,
    "price": 150.00,
    "price_cents": 15000,
    "is_active": true,
    "variable_pricing": true,
    "requires_deposit": true,
    "deposit_amount_cents": 5000,
    "deposit_percent": null,
    "prep_time": 10,
    "takedown_time": 15,
    "is_global": true
  }
]
```

---

#### `api.get_service_stats(service_id, days=30)`

Get booking statistics for a specific service.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `service_id` | int | ID of the service (required) |
| `days` | int | Days to analyze (default: 30, max: 90) |

**Returns:**
```json
{
  "service_id": 301,
  "service_name": "Haircut",
  "period_days": 30,
  "total_bookings": 145,
  "completed_bookings": 132,
  "canceled_bookings": 13,
  "completion_rate": 91.0,
  "total_revenue_cents": 594000,
  "total_revenue": 5940.00,
  "average_revenue_per_booking": 45.00
}
```

---

### Locations

#### `api.get_locations(**filters)`

Get business locations.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `is_active` | bool | Filter by active status (default: True) |
| `limit` | int | Max results (default: 50, max: 100) |

**Returns:**
```json
[
  {
    "id": 1,
    "name": "Downtown Salon",
    "address_line1": "123 Main Street",
    "address_line2": "Suite 100",
    "city": "Denver",
    "state": "CO",
    "postal_code": "80202",
    "country": "US",
    "phone": "+13035551234",
    "email": "downtown@salon.com",
    "timezone": "America/Denver",
    "is_active": true,
    "is_primary": true
  },
  {
    "id": 2,
    "name": "Cherry Creek Location",
    "address_line1": "456 Cherry Creek Dr",
    "address_line2": "",
    "city": "Denver",
    "state": "CO",
    "postal_code": "80206",
    "country": "US",
    "phone": "+13035555678",
    "email": "cherrycreek@salon.com",
    "timezone": "America/Denver",
    "is_active": true,
    "is_primary": false
  }
]
```

---

#### `api.get_location_stats(location_id, days=30)`

Get booking statistics for a specific location. **Requires:** `multi_location` feature.

**Returns:**
```json
{
  "location_id": 1,
  "location_name": "Downtown Salon",
  "period_days": 30,
  "resource_count": 5,
  "total_bookings": 320,
  "completed_bookings": 298,
  "canceled_bookings": 22,
  "completion_rate": 93.1
}
```

---

### Staff

#### `api.get_staff(**filters)`

Get staff members for this business.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `role` | string | Filter by role: `staff`, `manager`, `owner` |
| `is_active` | bool | Filter by active status (default: True) |
| `limit` | int | Max results (default: 100, max: 500) |

**Returns:**
```json
[
  {
    "id": 201,
    "email": "sarah@salon.com",
    "name": "Sarah Johnson",
    "first_name": "Sarah",
    "last_name": "Johnson",
    "role": "staff",
    "is_active": true,
    "phone": "+15551234567"
  },
  {
    "id": 202,
    "email": "mike@salon.com",
    "name": "Mike Chen",
    "first_name": "Mike",
    "last_name": "Chen",
    "role": "manager",
    "is_active": true,
    "phone": "+15559876543"
  }
]
```

---

#### `api.get_staff_performance(staff_id=None, days=30)`

Get performance statistics for staff members.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `staff_id` | int | Specific staff ID (optional) |
| `days` | int | Days to analyze (default: 30, max: 90) |

**Returns (all staff):**
```json
{
  "period_days": 30,
  "staff_count": 5,
  "staff": [
    {
      "staff_id": 201,
      "staff_name": "Sarah Johnson",
      "resource_id": 101,
      "resource_name": "Sarah Johnson",
      "total_appointments": 85,
      "completed_appointments": 80,
      "canceled_appointments": 3,
      "no_show_appointments": 2,
      "completion_rate": 94.1,
      "hours_worked": 72.5
    },
    {
      "staff_id": 202,
      "staff_name": "Mike Chen",
      "resource_id": 102,
      "resource_name": "Mike Chen",
      "total_appointments": 72,
      "completed_appointments": 68,
      "canceled_appointments": 4,
      "no_show_appointments": 0,
      "completion_rate": 94.4,
      "hours_worked": 65.0
    }
  ]
}
```

**Returns (single staff):**
```json
{
  "staff_id": 201,
  "staff_name": "Sarah Johnson",
  "resource_id": 101,
  "resource_name": "Sarah Johnson",
  "total_appointments": 85,
  "completed_appointments": 80,
  "canceled_appointments": 3,
  "no_show_appointments": 2,
  "completion_rate": 94.1,
  "hours_worked": 72.5
}
```

---

### Payments & Invoices

#### `api.get_payments(**filters)`

Get payment records. **Requires:** `payment_processing` feature.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status: `completed`, `pending`, `failed`, `refunded` |
| `days_back` | int | Get payments from last N days (default: 30, max: 365) |
| `limit` | int | Max results (default: 100, max: 500) |

**Returns:**
```json
[
  {
    "id": 8001,
    "amount_cents": 4500,
    "amount": 45.00,
    "status": "completed",
    "currency": "USD",
    "customer_id": 5001,
    "customer_email": "jane.doe@example.com",
    "customer_name": "Jane Doe",
    "description": "Haircut - Jan 15",
    "created_at": "2025-01-15T11:05:00+00:00",
    "completed_at": "2025-01-15T11:05:30+00:00"
  }
]
```

---

#### `api.get_invoices(**filters)`

Get invoices. **Requires:** `payment_processing` feature.

**Returns:**
```json
[
  {
    "id": 9001,
    "status": "paid",
    "currency": "USD",
    "subtotal_cents": 9900,
    "subtotal": 99.00,
    "tax_cents": 792,
    "tax": 7.92,
    "total_cents": 10692,
    "total": 106.92,
    "period_start": "2025-01-01T00:00:00+00:00",
    "period_end": "2025-01-31T23:59:59+00:00",
    "plan_name": "Pro Plan",
    "created_at": "2025-02-01T00:00:00+00:00",
    "paid_at": "2025-02-01T12:00:00+00:00"
  }
]
```

---

#### `api.get_revenue_stats(days=30)`

Get revenue statistics. **Requires:** `payment_processing` feature.

**Returns:**
```json
{
  "period_days": 30,
  "total_revenue_cents": 1250000,
  "total_revenue": 12500.00,
  "payment_count": 278,
  "average_payment_cents": 4496,
  "average_payment": 44.96,
  "by_day": {
    "2025-01-15": {
      "count": 12,
      "amount_cents": 54000
    },
    "2025-01-16": {
      "count": 15,
      "amount_cents": 67500
    }
  }
}
```

---

### Contracts

#### `api.get_contracts(**filters)`

Get contracts. **Requires:** `can_use_contracts` feature.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status: `PENDING`, `SIGNED`, `EXPIRED`, `VOIDED` |
| `customer_id` | int | Filter by customer ID |
| `limit` | int | Max results (default: 100, max: 500) |

**Returns:**
```json
[
  {
    "id": 601,
    "title": "Service Agreement",
    "status": "SIGNED",
    "customer_id": 5001,
    "customer_email": "jane.doe@example.com",
    "customer_name": "Jane Doe",
    "template_name": "Standard Service Agreement",
    "expires_at": "2026-01-15T00:00:00+00:00",
    "sent_at": "2025-01-10T14:00:00+00:00",
    "created_at": "2025-01-10T14:00:00+00:00"
  }
]
```

---

#### `api.get_expiring_contracts(days=30)`

Get contracts expiring soon. **Requires:** `can_use_contracts` feature.

**Returns:**
```json
[
  {
    "id": 601,
    "title": "Service Agreement",
    "customer_id": 5001,
    "customer_email": "jane.doe@example.com",
    "customer_name": "Jane Doe",
    "expires_at": "2025-02-10T00:00:00+00:00",
    "days_until_expiry": 25
  }
]
```

**Example:**
```python
# Send renewal reminders for expiring contracts
expiring = api.get_expiring_contracts(days=30)
for contract in expiring:
    api.send_email(
        contract['customer_email'],
        f"Contract Renewal Reminder",
        f"Hi {contract['customer_name']}, your contract expires in {contract['days_until_expiry']} days. "
        f"Please contact us to renew."
    )
```

---

### SMS

#### `api.send_sms(to, message)`

Send an SMS message. **Requires:** `sms_enabled` feature.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `to` | string | Phone number (E.164 format recommended) |
| `message` | string | Message content (max 1600 characters) |

**Returns:** `True` if sent successfully, `False` otherwise.

**Example:**
```python
# Send appointment reminder via SMS
appointments = api.get_appointments(start_date='2025-01-16')
for apt in appointments:
    customers = api.get_customers(limit=1)  # Get customer details
    for customer in customers:
        if customer.get('phone'):
            api.send_sms(
                customer['phone'],
                f"Reminder: Your appointment '{apt['title']}' is tomorrow at {apt['start_time'][:16]}"
            )
```

---

#### `api.get_sms_balance()`

Get SMS credit balance. **Requires:** `sms_enabled` feature.

**Returns:**
```json
{
  "credits_remaining": 450,
  "monthly_limit": 500,
  "credits_used_this_month": 50
}
```

---

### Email

#### `api.send_email(to, subject, body)`

Send a plain text email.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `to` | string/int | Email address or customer ID |
| `subject` | string | Email subject (max 200 chars) |
| `body` | string | Email body (max 10,000 chars) |

**Insertion codes available:**
- `{business_name}` - Business name
- `{business_email}` - Business email
- `{business_phone}` - Business phone
- `{today}` - Today's date (YYYY-MM-DD)
- `{now}` - Current datetime

**Returns:** `True` if sent successfully, `False` otherwise.

---

#### `api.get_email_templates(**filters)`

Get email templates. **Requires:** `can_use_email_templates` feature.

**Returns:**
```json
[
  {
    "id": 701,
    "name": "Appointment Reminder",
    "template_type": "reminder",
    "subject": "Reminder: Your appointment is coming up",
    "is_active": true,
    "created_at": "2025-01-01T00:00:00+00:00"
  }
]
```

---

#### `api.send_template_email(template_id, to, variables=None)`

Send an email using a template. **Requires:** `can_use_email_templates` feature.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `template_id` | int | ID of email template |
| `to` | string | Recipient email |
| `variables` | dict | Variables to substitute in template |

---

### Video Conferencing

#### `api.create_video_meeting(provider='zoom', title='Video Appointment', duration=60, start_time=None)`

Create a video meeting link. **Requires:** `can_add_video_conferencing` feature.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `provider` | string | `zoom`, `google_meet`, or `teams` |
| `title` | string | Meeting title |
| `duration` | int | Duration in minutes |
| `start_time` | string | ISO datetime (optional) |

**Returns:**
```json
{
  "provider": "zoom",
  "meeting_id": "a1b2c3d4e5f6g7h8",
  "title": "Video Consultation",
  "duration": 60,
  "join_url": "https://zoom.example.com/j/a1b2c3d4e5f6g7h8",
  "host_url": "https://zoom.example.com/h/a1b2c3d4e5f6g7h8",
  "password": "abc12345",
  "created_at": "2025-01-15T10:00:00+00:00"
}
```

**Example:**
```python
# Create video links for virtual appointments
appointments = api.get_appointments(status='SCHEDULED')
for apt in appointments:
    if 'virtual' in apt['title'].lower():
        meeting = api.create_video_meeting(
            provider='zoom',
            title=apt['title'],
            duration=60
        )
        api.update_appointment(apt['id'], notes=f"Join URL: {meeting['join_url']}")
```

---

### Analytics

#### `api.get_analytics(**filters)`

Get comprehensive analytics. **Requires:** `advanced_reporting` feature.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `days` | int | Days to analyze (default: 30, max: 90) |
| `metrics` | list | Metrics to include: `bookings`, `customers`, `services`, `staff` |

**Returns:**
```json
{
  "period_days": 30,
  "period_start": "2024-12-15T00:00:00+00:00",
  "period_end": "2025-01-15T00:00:00+00:00",
  "bookings": {
    "total": 320,
    "completed": 298,
    "canceled": 22,
    "completion_rate": 93.1
  },
  "customers": {
    "total": 1250,
    "with_email": 1180,
    "with_phone": 890
  },
  "services": {
    "total": 15,
    "active": 12
  },
  "staff": {
    "total": 8,
    "active": 7
  }
}
```

---

#### `api.get_booking_trends(days=30, group_by='day')`

Get booking trends over time. **Requires:** `advanced_reporting` feature.

**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `days` | int | Days to analyze (default: 30, max: 90) |
| `group_by` | string | `day`, `week`, `hour`, or `weekday` |

**Returns (group_by='day'):**
```json
{
  "period_days": 30,
  "group_by": "day",
  "data": {
    "2025-01-15": {
      "total": 12,
      "completed": 11,
      "canceled": 1
    },
    "2025-01-16": {
      "total": 15,
      "completed": 14,
      "canceled": 1
    }
  }
}
```

**Returns (group_by='weekday'):**
```json
{
  "period_days": 30,
  "group_by": "weekday",
  "data": {
    "Monday": {
      "total": 45,
      "completed": 42,
      "canceled": 3
    },
    "Tuesday": {
      "total": 52,
      "completed": 50,
      "canceled": 2
    }
  }
}
```

**Returns (group_by='hour'):**
```json
{
  "period_days": 30,
  "group_by": "hour",
  "data": {
    "9": {
      "total": 28,
      "completed": 26,
      "canceled": 2
    },
    "10": {
      "total": 35,
      "completed": 33,
      "canceled": 2
    }
  }
}
```

---

### HTTP Requests

HTTP methods require URLs to be whitelisted. Contact support at pluginaccess@smoothschedule.com to request whitelisting.

#### `api.http_get(url, headers=None)`
#### `api.http_post(url, data=None, headers=None)`
#### `api.http_put(url, data=None, headers=None)`
#### `api.http_patch(url, data=None, headers=None)`
#### `api.http_delete(url, headers=None)`

**Returns:** Response text from the URL.

**Example:**
```python
# Call external webhook (URL must be whitelisted)
response = api.http_post(
    'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
    data={
        'text': 'New appointment booked!',
        'channel': '#bookings'
    }
)
```

---

### Utilities

#### `api.log(message)`

Log a message for debugging.

```python
api.log("Processing complete!")
```

---

#### `api.count(items)`

Count items in a list.

```python
appointments = api.get_appointments()
total = api.count(appointments)  # Returns: 50
```

---

#### `api.sum(items)`

Sum numeric items.

```python
payments = api.get_payments(status='completed')
amounts = [p['amount_cents'] for p in payments]
total = api.sum(amounts)  # Returns: 125000
```

---

#### `api.filter(items, condition)`

Filter items by condition.

```python
customers = api.get_customers()
with_phone = api.filter(customers, lambda c: c.get('phone'))
```

---

## Complete Example Plugin

```python
# Daily Business Report Plugin
#
# Sends a daily summary email to the business owner
# including bookings, revenue, and staff performance.

# Get today's appointments
from datetime import datetime
today = datetime.now().strftime('%Y-%m-%d')

appointments = api.get_appointments(start_date=today, limit=100)
completed = api.filter(appointments, lambda a: a['status'] == 'COMPLETED')
scheduled = api.filter(appointments, lambda a: a['status'] == 'SCHEDULED')

# Get staff performance
staff_stats = api.get_staff_performance(days=1)

# Build the report
report = f"""
Daily Business Report - {today}
================================

APPOINTMENTS TODAY
------------------
Total Scheduled: {len(scheduled)}
Completed: {len(completed)}
Completion Rate: {round(len(completed) / len(appointments) * 100, 1) if appointments else 0}%

STAFF PERFORMANCE
-----------------
"""

for staff in staff_stats.get('staff', []):
    report += f"\n{staff['staff_name']}: {staff['completed_appointments']} completed, {staff['hours_worked']}h worked"

# Send the report
api.send_email(
    'owner@business.com',
    f'Daily Report - {today}',
    report
)

api.log("Daily report sent successfully!")
result = {'status': 'success', 'appointments_processed': len(appointments)}
```

---

## Error Handling

All API methods may raise `ScriptExecutionError` for:
- Invalid parameters
- Resource not found
- Feature not available on plan
- Rate limits exceeded

```python
# Errors are raised as exceptions
try:
    resource = api.get_resource_availability(99999)  # Non-existent ID
except Exception as e:
    api.log(f"Error: {e}")
```
