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

# Custom Function Actions

> Built-in actions available to your custom function code — connect to HTTP APIs, Airtable, Google Sheets, WhatsApp, Shopify, and SubVerse agents without writing any integration boilerplate

Custom Function Actions are pre-built integrations you can call directly from your custom function code via `subverseActions`. When you build a custom function, you select which actions you want to use and attach the required credentials. The Code Assistant is aware of your selected actions and credentials, so you can describe what you want in plain English and it will generate the correct code for you.

<Info>
  Actions that require credentials will prompt you to select a saved credential when you add the action to your function. Credentials are stored securely and injected at runtime — your code never contains secrets.
</Info>

***

## HTTP Send Request

Make an outbound HTTP request to any URL. Supports all standard methods, custom headers, query parameters, and request body. Optionally authenticates using a stored credential.

**Compatible credentials:** HTTP Basic Auth, HTTP Bearer Auth, HTTP Header Auth (API Key), HTTP Digest Auth, HTTP Query Auth, HTTP Custom Auth

| Parameter                            | Required | Description                                                                                                               |
| ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `options.method`                     | Yes      | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`                                                   |
| `options.url`                        | Yes      | Target URL — must start with `http://` or `https://`                                                                      |
| `options.headers`                    | No       | Array of `{ name, value }` pairs for custom request headers                                                               |
| `options.query`                      | No       | Array of `{ name, value }` pairs appended as URL query parameters                                                         |
| `options.body`                       | No       | Request payload — object (auto-serialised as JSON) or raw string                                                          |
| `credentialId`                       | No       | ID of the saved credential to use                                                                                         |
| `options.options.timeout`            | No       | Request timeout in milliseconds. Default `30000`, max `300000`                                                            |
| `options.options.followRedirects`    | No       | Follow 301/302 redirects. Default `true`                                                                                  |
| `options.options.ignoreSSL`          | No       | Bypass SSL certificate validation. Default `false`                                                                        |
| `options.options.responseFormat`     | No       | Force response format: `autodetect` \| `json` \| `text` \| `base64`. Default `autodetect`. Use `base64` for binary files. |
| `options.options.fullResponse`       | No       | Return `statusCode`, `headers`, and `body` instead of just the body. Default `false`                                      |
| `options.options.ignoreResponseCode` | No       | Don't throw on 4xx/5xx — handle the error response yourself. Default `false`                                              |

```javascript theme={null}
const response = await subverseActions.http.sendRequest({
  credentialId: 'CREDENTIAL_ID', //optional
  options: {
    method: 'POST',
    url: 'https://api.example.com/orders',
    headers: [
      { name: 'custom-headers', value: 'custom-value' }
    ],
    body: {
      customerId: body.params.customer_id,
      status: 'confirmed'
    },
    options: {
      timeout: 10000,
      ignoreResponseCode: true             // handle 4xx without throwing
    }
  }
});

// response.responseCode — HTTP status code
// response.data        — parsed response body
// response.message     — status text
```

***

## Airtable List Records

Fetch a list of records from an Airtable table. Supports filtering, sorting, field selection, and pagination.

| Parameter                         | Required | Description                                          |
| --------------------------------- | -------- | ---------------------------------------------------- |
| `credentialId`                    | Yes      | Airtable credential ID                               |
| `options.baseId`                  | Yes      | Airtable Base ID — e.g. `appXXXXXXXXXXXXXX`          |
| `options.tableIdOrName`           | Yes      | Table name or table ID — e.g. `tblXXXXXXXXXXXXXX`    |
| `options.filters.filterByFormula` | No       | Airtable formula to filter records                   |
| `options.filters.fields`          | No       | Array of field names to return. Omit for all fields  |
| `options.filters.sort`            | No       | Array of `{ "Field Name": "asc" \| "desc" }` objects |
| `options.filters.pageSize`        | No       | Records per page, max `100`                          |
| `options.filters.maxRecords`      | No       | Total cap on records returned                        |
| `options.filters.view`            | No       | Name or ID of a specific view to apply               |

```javascript theme={null}
const result = await subverseActions.airtable.getRecordList({
  credentialId: 'CREDENTIAL_ID',
  options: {
    baseId: 'appXXXXXXXXXXXXXX',
    tableIdOrName: 'Customers',
    filters: {
      filterByFormula: `{Email} = '${body.params.email}'`,
      fields: ['Name', 'Email', 'Status'],
      pageSize: 10
    }
  }
});

// result.records — array of { id, createdTime, fields }
const customer = result.records[0];
```

## Airtable Get Record by ID

Retrieve a single Airtable record by its record ID.

| Parameter               | Required | Description                          |
| ----------------------- | -------- | ------------------------------------ |
| `credentialId`          | Yes      | Airtable credential ID               |
| `options.baseId`        | Yes      | Airtable Base ID                     |
| `options.tableIdOrName` | Yes      | Table name or table ID               |
| `options.recordId`      | Yes      | Record ID — e.g. `recXXXXXXXXXXXXXX` |

```javascript theme={null}
const record = await subverseActions.airtable.getRecordById({
  credentialId: 'CREDENTIAL_ID',
  options: {
    baseId: 'appXXXXXXXXXXXXXX',
    tableIdOrName: 'Orders',
    recordId: body.params.record_id
  }
});

// record.id, record.createdTime, record.fields
```

## Airtable Create Record

Create one or more records in an Airtable table. Use `fields` for a single record or `records` for bulk creation.

| Parameter               | Required | Description                                                |
| ----------------------- | -------- | ---------------------------------------------------------- |
| `credentialId`          | Yes      | Airtable credential ID                                     |
| `options.baseId`        | Yes      | Airtable Base ID                                           |
| `options.tableIdOrName` | Yes      | Table name or table ID                                     |
| `options.fields`        | One of   | Field key-value pairs for a single record                  |
| `options.records`       | One of   | Array of `{ fields: { ... } }` objects for bulk creation   |
| `options.typecast`      | No       | Auto-convert strings to match field types. Default `false` |

```javascript theme={null}
// Single record
const created = await subverseActions.airtable.createRecord({
  credentialId: 'CREDENTIAL_ID',
  options: {
    baseId: 'appXXXXXXXXXXXXXX',
    tableIdOrName: 'Leads',
    fields: {
      Name: body.params.name,
      Email: body.params.email,
      Source: 'Voice Call'
    }
  }
});

// created.id — new record ID
```

## Airtable Update Record

Update specific fields of an existing Airtable record. Only provided fields are changed; others remain untouched.

| Parameter               | Required | Description                                                |
| ----------------------- | -------- | ---------------------------------------------------------- |
| `credentialId`          | Yes      | Airtable credential ID                                     |
| `options.baseId`        | Yes      | Airtable Base ID                                           |
| `options.tableIdOrName` | Yes      | Table name or table ID                                     |
| `options.recordId`      | Yes      | Record ID to update                                        |
| `options.fields`        | Yes      | Object of fields to update                                 |
| `options.typecast`      | No       | Auto-convert strings to match field types. Default `false` |

```javascript theme={null}
const updated = await subverseActions.airtable.updateRecord({
  credentialId: 'CREDENTIAL_ID',
  options: {
    baseId: 'appXXXXXXXXXXXXXX',
    tableIdOrName: 'Orders',
    recordId: body.params.record_id,
    fields: {
      Status: 'Completed',
      ResolvedAt: new Date().toISOString()
    }
  }
});
```

## Airtable Delete Record

Permanently delete a single Airtable record by its record ID.

| Parameter               | Required | Description            |
| ----------------------- | -------- | ---------------------- |
| `credentialId`          | Yes      | Airtable credential ID |
| `options.baseId`        | Yes      | Airtable Base ID       |
| `options.tableIdOrName` | Yes      | Table name or table ID |
| `options.recordId`      | Yes      | Record ID to delete    |

```javascript theme={null}
const result = await subverseActions.airtable.deleteRecord({
  credentialId: 'CREDENTIAL_ID',
  options: {
    baseId: 'appXXXXXXXXXXXXXX',
    tableIdOrName: 'TempRecords',
    recordId: body.params.record_id
  }
});

// result.deleted — true on success
```

***

## Google Sheets Read Rows

Read values from a range in a Google Sheet. Returns a 2D array where each inner array is a row. Optionally filter rows by column values in-memory — no extra API calls needed.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API, Google Sheets API Key (public sheets only)

| Parameter                   | Required | Description                                                                                                                             |
| --------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `credentialId`              | No       | Required for service account and OAuth2. Optional for API key on public sheets                                                          |
| `options.spreadsheetId`     | Yes      | Spreadsheet ID from the URL: `/spreadsheets/d/{spreadsheetId}/`                                                                         |
| `options.range`             | Yes      | A1 notation range — e.g. `Sheet1!A1:D10` or just `Sheet1`                                                                               |
| `options.valueRenderOption` | No       | `FORMATTED_VALUE` (default), `FORMULA`, or `UNFORMATTED_VALUE`                                                                          |
| `options.filters`           | No       | Array of `{ columnName, value }` filters. The first row of the range is treated as the header row. Only matching data rows are returned |
| `options.combineFilters`    | No       | `AND` (default) requires all filters to match; `OR` requires any filter to match                                                        |

```javascript theme={null}
const data = await subverseActions.googleSheets.readRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A1:D50',
    valueRenderOption: 'FORMATTED_VALUE'
  }
});

// data.values — 2D array; data.values[0] is usually the header row
const headers = data.values[0];
const rows = data.values.slice(1);
```

### Read Rows with Filtering

Filter rows in-memory by column name without making extra API calls. The first row of the range is used as the header row for column name lookup.

```javascript theme={null}
// Return only rows where Status = 'Active'
const data = await subverseActions.googleSheets.readRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A1:D50',
    filters: [{ columnName: 'Status', value: 'Active' }]
  }
});

// Multiple filters with OR logic
const data2 = await subverseActions.googleSheets.readRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A1:D50',
    filters: [
      { columnName: 'Status', value: 'Active' },
      { columnName: 'Status', value: 'Pending' }
    ],
    combineFilters: 'OR'
  }
});
```

## Google Sheets Append Rows

Append rows to the end of a Google Sheet after the last row with data. Rows can be positional arrays or key-value objects — when objects are used, the executor reads the sheet header and auto-maps values to columns by name.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter                  | Required | Description                                                                                                                                               |
| -------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credentialId`             | Yes      | Google Sheets credential ID (service account or OAuth2)                                                                                                   |
| `options.spreadsheetId`    | Yes      | Spreadsheet ID                                                                                                                                            |
| `options.range`            | Yes      | Sheet name or range — only the sheet name matters for append (e.g. `Sheet1`)                                                                              |
| `options.rows`             | Yes      | Array of rows to append. Each row is either an array of cell values (positional) or a key-value object whose keys match column header names (auto-mapped) |
| `options.valueInputOption` | No       | `RAW` (as-is) or `USER_ENTERED` (parsed). Default `USER_ENTERED`                                                                                          |
| `options.headerRow`        | No       | 1-based row number containing column headers. Used only when rows are objects. Default `1`                                                                |

```javascript theme={null}
const result = await subverseActions.googleSheets.appendRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Leads',
    rows: [
      [body.params.name, body.params.email, new Date().toISOString()]
    ]
  }
});

// result.updates.updatedRows — number of rows added
```

### Append Rows with Auto-Mapping (Object Rows)

Pass key-value objects instead of positional arrays. The executor reads the header row and maps each key to the corresponding column automatically.

```javascript theme={null}
const result = await subverseActions.googleSheets.appendRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Leads',
    rows: [
      { Name: body.params.name, Email: body.params.email, CreatedAt: new Date().toISOString() }
    ]
  }
});

// If the sheet has a title in row 1 and metadata in row 2, set headerRow: 3
const result2 = await subverseActions.googleSheets.appendRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Leads!A1:Z',
    rows: [{ Name: 'Alice', Email: 'alice@example.com' }],
    headerRow: 3
  }
});
```

## Google Sheets Update Rows

Overwrite existing rows at a specific range. The data dimensions must match the range provided. Optionally, use `keyColumn` + `keyValue` to find a specific row by column name and update only that row — when combined with object rows, only the provided fields are written (partial update), preserving other column values.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter                  | Required | Description                                                                                                                   |
| -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `credentialId`             | Yes      | Google Sheets credential ID (service account or OAuth2)                                                                       |
| `options.spreadsheetId`    | Yes      | Spreadsheet ID                                                                                                                |
| `options.range`            | Yes      | A1 notation range to overwrite — e.g. `Sheet1!A2:C2`. When `keyColumn` is used, this range is read to locate the matching row |
| `options.rows`             | Yes      | Array of rows. Each row is either an array of cell values (positional) or a key-value object (auto-mapped)                    |
| `options.valueInputOption` | No       | `RAW` or `USER_ENTERED`. Default `USER_ENTERED`                                                                               |
| `options.headerRow`        | No       | 1-based row number containing column headers. Default `1`                                                                     |
| `options.keyColumn`        | No       | When set, finds the first row where this column's value equals `keyValue` and updates only that row                           |
| `options.keyValue`         | No       | Required when `keyColumn` is set. The value to search for in the key column                                                   |

```javascript theme={null}
const result = await subverseActions.googleSheets.updateRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: `Customers!B${body.params.row_number}:C${body.params.row_number}`,
    rows: [
      ['Completed', new Date().toISOString()]
    ]
  }
});
```

### Update Rows with Key-Based Find-and-Update

Find a row by column name and value, then update only that row in a single call. No need to read the sheet first or compute row numbers manually.

```javascript theme={null}
// Find the row where Email = 'john@example.com' and update the Status column
const result = await subverseActions.googleSheets.updateRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A1:D50',
    rows: [{ Status: 'Paid' }],  // only update Status, preserve other columns
    keyColumn: 'Email',
    keyValue: 'john@example.com'
  }
});
```

### Update Rows with Auto-Mapping (Object Rows)

When `keyColumn` is not set, object rows are auto-mapped to positional arrays using the sheet header and the entire range is overwritten.

```javascript theme={null}
const result = await subverseActions.googleSheets.updateRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A2:C2',
    rows: [{ Name: 'Updated', Email: 'updated@example.com', Status: 'Active' }]
  }
});
```

## Google Sheets Append or Update Rows

Upsert rows by a lookup column. For each row, if an existing row has a matching value at the lookup column, that row is updated in place; otherwise the row is appended after the last row with data. The lookup column can be specified by index (`lookupColumnIndex`) or by name (`keyColumn`). Rows can be positional arrays or key-value objects that are auto-mapped to columns by header name.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter                   | Required | Description                                                                                                          |
| --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `credentialId`              | Yes      | Google Sheets credential ID (service account or OAuth2)                                                              |
| `options.spreadsheetId`     | Yes      | Spreadsheet ID                                                                                                       |
| `options.range`             | Yes      | A1 notation range to search and write within (e.g. `Sheet1!A1:D` or `Sheet1`)                                        |
| `options.rows`              | Yes      | Array of rows to upsert. Each row is either an array of cell values (positional) or a key-value object (auto-mapped) |
| `options.lookupColumnIndex` | No\*     | 0-based column index used to match rows (0 = column A). Either this or `keyColumn` is required                       |
| `options.keyColumn`         | No\*     | Header name of the column used to match rows (e.g. `Email`). Either this or `lookupColumnIndex` is required          |
| `options.valueInputOption`  | No       | `RAW` or `USER_ENTERED`. Default `USER_ENTERED`                                                                      |
| `options.headerRow`         | No       | 1-based row number containing column headers. Used for `keyColumn` resolution and auto-mapping. Default `1`          |

\* Either `lookupColumnIndex` or `keyColumn` must be provided.

```javascript theme={null}
const result = await subverseActions.googleSheets.appendOrUpdateRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A1:D',
    rows: [
      [body.params.email, body.params.name, body.params.phone, 'Active']
    ],
    lookupColumnIndex: 0  // match on column A (email)
  }
});

// result.appendResult — present if new rows were appended
// result.updateResults — array of update results for matched rows
```

### Append or Update with Key Column by Name

Use `keyColumn` instead of `lookupColumnIndex` to reference the lookup column by its header name. The executor reads the header row and resolves the index dynamically.

```javascript theme={null}
const result = await subverseActions.googleSheets.appendOrUpdateRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A1:D',
    rows: [
      { Email: 'john@example.com', Name: 'John', Phone: '555-1234', Status: 'Active' },
      { Email: 'jane@example.com', Name: 'Jane', Phone: '555-5678', Status: 'Inactive' }
    ],
    keyColumn: 'Email'
  }
});
```

### Append or Update with Mixed Batch

A single call can update existing rows and append new ones. Rows matching an existing value in the key column are updated; the rest are appended.

```javascript theme={null}
const result = await subverseActions.googleSheets.appendOrUpdateRows({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Customers!A1:D',
    rows: [
      { Email: 'john@example.com', Name: 'John Updated', Status: 'Active' },  // existing → updated
      { Email: 'new@example.com', Name: 'New User', Status: 'Pending' }       // new → appended
    ],
    keyColumn: 'Email'
  }
});

// result.updateResults — array of update results for matched rows
// result.appendResult — present if new rows were appended
```

## Google Sheets Clear Range

Clear values from a range in a Google Sheet. Cell values are removed but formatting and data validation rules are preserved.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter               | Required | Description                                             |
| ----------------------- | -------- | ------------------------------------------------------- |
| `credentialId`          | Yes      | Google Sheets credential ID (service account or OAuth2) |
| `options.spreadsheetId` | Yes      | Spreadsheet ID                                          |
| `options.range`         | Yes      | A1 notation range to clear — e.g. `Sheet1!A1:D10`       |

```javascript theme={null}
const result = await subverseActions.googleSheets.clearRange({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    range: 'Sheet1!A2:D100'
  }
});

// result.cleared — true on success
```

## Google Sheets Create Sheet

Create a new sheet (tab) within an existing Google Spreadsheet. Returns the new sheet's numeric ID and title.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter               | Required | Description                                             |
| ----------------------- | -------- | ------------------------------------------------------- |
| `credentialId`          | Yes      | Google Sheets credential ID (service account or OAuth2) |
| `options.spreadsheetId` | Yes      | Spreadsheet to add the sheet to                         |
| `options.title`         | Yes      | Title for the new tab — must be non-empty and unique    |
| `options.rowCount`      | No       | Number of rows. Default `1000`                          |
| `options.columnCount`   | No       | Number of columns. Default `26`                         |

```javascript theme={null}
const result = await subverseActions.googleSheets.createSheet({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    title: 'February 2025',
    rowCount: 500,
    columnCount: 10
  }
});

// result.sheetId — numeric ID of the new sheet
// result.title — the title of the new sheet
```

## Google Sheets Delete Sheet

Permanently delete a sheet (tab) from a Google Spreadsheet by its numeric sheet ID. This action cannot be undone.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter               | Required | Description                                             |
| ----------------------- | -------- | ------------------------------------------------------- |
| `credentialId`          | Yes      | Google Sheets credential ID (service account or OAuth2) |
| `options.spreadsheetId` | Yes      | Spreadsheet ID                                          |
| `options.sheetId`       | Yes      | Numeric ID of the sheet (tab) to delete                 |

```javascript theme={null}
const result = await subverseActions.googleSheets.deleteSheet({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    sheetId: 1234567890
  }
});

// result.deleted — true on success
```

## Google Sheets Delete Rows or Columns

Delete one or more ranges of rows or columns from a Google Sheet. Uses 0-based indices — rows or columns after the deleted range shift to fill the gap. Supports deleting multiple non-contiguous ranges in a single request.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter                     | Required | Description                                                              |
| ----------------------------- | -------- | ------------------------------------------------------------------------ |
| `credentialId`                | Yes      | Google Sheets credential ID (service account or OAuth2)                  |
| `options.spreadsheetId`       | Yes      | Spreadsheet ID                                                           |
| `options.dimension`           | Yes      | `ROWS` or `COLUMNS`                                                      |
| `options.ranges`              | Yes      | Array of `{ sheetId, startIndex, endIndex }` — at least one required     |
| `options.ranges[].sheetId`    | Yes      | Numeric sheet ID (tab ID)                                                |
| `options.ranges[].startIndex` | Yes      | 0-based index of first row/column to delete (row 1 = index 0)            |
| `options.ranges[].endIndex`   | Yes      | 0-based index after the last row/column to delete (must be > startIndex) |

```javascript theme={null}
const result = await subverseActions.googleSheets.deleteRowsOrColumns({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID',
    dimension: 'ROWS',
    ranges: [
      { sheetId: 0, startIndex: 2, endIndex: 5 }  // deletes rows 3, 4, 5 (1-based)
    ]
  }
});

// result.deleted — true on success
```

## Google Sheets Create Spreadsheet

Create a new Google Spreadsheet owned by the authenticated account. Requires a title. Returns the new spreadsheet ID and URL.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter        | Required | Description                                             |
| ---------------- | -------- | ------------------------------------------------------- |
| `credentialId`   | Yes      | Google Sheets credential ID (service account or OAuth2) |
| `options.title`  | Yes      | Spreadsheet title — must be non-empty                   |
| `options.locale` | No       | Spreadsheet locale (e.g. `en_US`). Default `en_US`      |

```javascript theme={null}
const result = await subverseActions.googleSheets.createSpreadsheet({
  credentialId: 'CREDENTIAL_ID',
  options: {
    title: 'Monthly Sales Report',
    locale: 'en_US'
  }
});

// result.spreadsheetId — ID of the new spreadsheet
// result.spreadsheetUrl — URL to open the spreadsheet in Google Sheets
```

## Google Sheets Delete Spreadsheet

Move a Google Spreadsheet to trash by its ID. Uses the Google Drive API — the spreadsheet is trashed (not permanently destroyed) and can be restored from Google Drive trash.

**Compatible credentials:** Google Sheets Service Account, Google Sheets OAuth2 API

| Parameter               | Required | Description                                             |
| ----------------------- | -------- | ------------------------------------------------------- |
| `credentialId`          | Yes      | Google Sheets credential ID (service account or OAuth2) |
| `options.spreadsheetId` | Yes      | Spreadsheet ID to delete                                |

```javascript theme={null}
const result = await subverseActions.googleSheets.deleteSpreadsheet({
  credentialId: 'CREDENTIAL_ID',
  options: {
    spreadsheetId: 'YOUR_SPREADSHEET_ID'
  }
});

// result.deleted — true on success
```

***

## SubVerse Agents Trigger Voice Call

All agent trigger actions authenticate via an `httpHeaderAuth` credential carrying the workspace API key. Set the header name to `x-api-key` and value to your workspace API key.

Trigger an outbound voice call to a customer phone number. The call is queued immediately and dispatched by the voice infrastructure.

| Parameter                           | Required | Description                                                              |
| ----------------------------------- | -------- | ------------------------------------------------------------------------ |
| `credentialId`                      | Yes      | `httpHeaderAuth` credential with `x-api-key` value                       |
| `options.phoneNumber`               | Yes      | Customer number in E.164 format — e.g. `+919876543210`                   |
| `options.agentName`                 | Yes      | Name of the agent (use case) to run for this call                        |
| `options.agentNumber`               | No       | Outbound caller ID. Uses workspace default if omitted                    |
| `options.metadata`                  | No       | Key-value pairs passed into the agent session — use `{{key}}` in prompts |
| `options.scheduleTime`              | No       | ISO 8601 UTC datetime to place the call — e.g. `2025-06-01T09:00:00Z`    |
| `options.startWorkingHour`          | No       | Earliest time to call, `HH:MM` format — e.g. `09:00`                     |
| `options.endWorkingHour`            | No       | Latest time to call, `HH:MM` format — e.g. `18:00`                       |
| `options.timezone`                  | No       | IANA timezone for working hours — e.g. `Asia/Kolkata`                    |
| `options.noOfRetries`               | No       | Retry count on failure. Default `0`                                      |
| `options.callPriority`              | No       | Queue priority `1` (highest) – `100` (lowest). Default `10`              |
| `options.options.initialMessage`    | No       | Opening line spoken when the call connects                               |
| `options.options.additionalContext` | No       | Extra prompt instructions for this call only                             |

```javascript theme={null}
const call = await subverseActions.agent.callOutboundTrigger({
  credentialId: 'CREDENTIAL_ID',
  options: {
    phoneNumber: body.params.phone_number,
    agentName: 'payment-reminder',
    metadata: {
      customerName: body.params.name,
      amountDue: body.params.amount
    },
    options: {
      initialMessage: `Hello ${body.params.name}, this is a reminder about your upcoming payment.`
    },
    startWorkingHour: '09:00',
    endWorkingHour: '18:00',
    timezone: 'Asia/Kolkata'
  }
});

// call.responseCode — 200 on success
// call.data.jobId   — queue job ID for tracking
```

## SubVerse Agents Trigger Chat Message

Send a WhatsApp message or start a chat agent session for a customer.

| Parameter                                   | Required | Description                                                                                                          |
| ------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `credentialId`                              | Yes      | `httpHeaderAuth` credential with `x-api-key` value                                                                   |
| `options.communicationChannel`              | Yes      | Name of the WhatsApp channel in your workspace                                                                       |
| `options.customerNumber`                    | Yes      | Recipient WhatsApp number in E.164 format                                                                            |
| `options.whatsappOptions.messageType`       | No       | `say` (verbatim) \| `prompt` (LLM-generated) \| `template`. Default `prompt`                                         |
| `options.whatsappOptions.message`           | No       | Message text — required when `messageType` is `say`                                                                  |
| `options.whatsappOptions.additionalContext` | No       | Injected into LLM prompt — used when `messageType` is `prompt`                                                       |
| `options.whatsappOptions.templateId`        | No       | WhatsApp template name — required when `messageType` is `template`                                                   |
| `options.whatsappOptions.templateLanguage`  | No       | BCP-47 language code for the template (e.g. `en`, `en_US`, `hi`). Default `en`                                       |
| `options.whatsappOptions.templateVariables` | No       | Key/value pairs substituted into the WhatsApp template body placeholders. Only used when `messageType` is `template` |
| `options.metadata`                          | No       | Key-value pairs passed into the agent session                                                                        |
| `options.scheduleTime`                      | No       | ISO 8601 datetime to schedule the send                                                                               |
| `options.agentName`                         | No       | Override the channel's default agent                                                                                 |

```javascript theme={null}
const chat = await subverseActions.agent.chatTrigger({
  credentialId: 'CREDENTIAL_ID',
  options: {
    communicationChannel: 'whatsapp-support',
    customerNumber: body.params.whatsapp_number,
    whatsappOptions: {
      messageType: 'say',
      message: `Hi ${body.params.name}, your order #${body.params.order_id} has been shipped!`
    },
    metadata: {
      customerName: body.params.name
    }
  }
});

// chat.data.sessionId — session ID for the triggered conversation
```

<Note title="Template messages">
  When `messageType` is `template`, provide `templateId`, `templateLanguage`, and `templateVariables` to fill the template's placeholders. `templateVariables` is decoupled from `metadata` — only `templateVariables` is used for template substitution.

  ```javascript theme={null}
  const chat = await subverseActions.agent.chatTrigger({
    credentialId: 'CREDENTIAL_ID',
    options: {
      communicationChannel: 'whatsapp-support',
      customerNumber: body.params.whatsapp_number,
      whatsappOptions: {
        messageType: 'template',
        templateId: 'order_confirmation',
        templateLanguage: 'en_US',
        templateVariables: {
          customer_name: body.params.name,
          order_id: body.params.order_id
        }
      }
    }
  });
  ```
</Note>

## SubVerse Agents Trigger Email

Send an email or start an email agent session for a customer.

| Parameter                                | Required | Description                                                                                                                                                                                                                                       |
| ---------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credentialId`                           | Yes      | `httpHeaderAuth` credential with `x-api-key` value                                                                                                                                                                                                |
| `options.communicationChannel`           | Yes      | Name of the Email channel in your workspace                                                                                                                                                                                                       |
| `options.customerEmail`                  | Yes      | Recipient email address                                                                                                                                                                                                                           |
| `options.emailOptions.messageType`       | No       | `say` (verbatim body) \| `prompt` (LLM-generated). Default `prompt`                                                                                                                                                                               |
| `options.emailOptions.subject`           | No       | Email subject line                                                                                                                                                                                                                                |
| `options.emailOptions.body`              | No       | Email body (HTML or plain text) — required when `messageType` is `say`                                                                                                                                                                            |
| `options.emailOptions.additionalContext` | No       | Injected into LLM prompt — used when `messageType` is `prompt`                                                                                                                                                                                    |
| `options.emailOptions.replyTo`           | No       | Optional sessionId of the existing email session to reply to. When provided, the outbound email is threaded onto that session with the correct In-Reply-To/References, accumulated Cc/Bcc, and quoted context. Omit to start a fresh email thread |
| `options.emailOptions.cc`                | No       | CC recipients — comma-separated                                                                                                                                                                                                                   |
| `options.metadata`                       | No       | Key-value pairs passed into the agent session                                                                                                                                                                                                     |
| `options.scheduleTime`                   | No       | ISO 8601 datetime to schedule the send                                                                                                                                                                                                            |

```javascript theme={null}
const email = await subverseActions.agent.emailTrigger({
  credentialId: 'CREDENTIAL_ID',
  options: {
    communicationChannel: 'email-support',
    customerEmail: body.params.email,
    emailOptions: {
      messageType: 'say',
      subject: `Your order #${body.params.order_id} is confirmed`,
      body: `<p>Hi ${body.params.name},</p><p>Thank you for your order. We'll notify you when it ships.</p>`
    },
    metadata: {
      customerName: body.params.name
    }
  }
});

// email.data.sessionId — session ID for the triggered conversation
```

***

## Email Send

Send an email directly through an SMTP server using a saved **Email (SMTP / IMAP)** credential. This action is useful for transactional emails, alerts, and reports from a custom function.

**Compatible credentials:** [Email (SMTP / IMAP)](/credentials/types/email-smtp)

| Parameter                   | Required | Description                                                                                                                                               |
| --------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credentialId`              | Yes      | Email (SMTP / IMAP) credential ID                                                                                                                         |
| `options.fromEmail`         | No       | Sender address in `sender@example.com` or `Name <sender@example.com>` format. Defaults to the credential username when omitted                            |
| `options.toEmail`           | Yes      | Recipient email address                                                                                                                                   |
| `options.subject`           | Yes      | Email subject line                                                                                                                                        |
| `options.emailFormat`       | No       | `text`, `html`, or `both`. Default `both`                                                                                                                 |
| `options.text`              | No       | Plain text body. Provide it when `emailFormat` is `text` or `both`                                                                                        |
| `options.html`              | No       | HTML body. Provide it when `emailFormat` is `html` or `both`                                                                                              |
| `options.ccEmail`           | No       | CC recipient address                                                                                                                                      |
| `options.bccEmail`          | No       | BCC recipient address                                                                                                                                     |
| `options.replyTo`           | No       | Reply-To address                                                                                                                                          |
| `options.attachments`       | No       | Array of `{ fileUrl, fileName? }` objects. `fileUrl` is required and must be a valid URL; `fileName` is optional and is derived from the URL when omitted |
| `options.ignoreSSL`         | No       | Bypass SSL certificate validation. Default `false`                                                                                                        |
| `options.appendAttribution` | No       | Append a "Sent via SubverseAI" footer. Default `false`                                                                                                    |

```javascript theme={null}
const result = await subverseActions.email.send({
  credentialId: 'CREDENTIAL_ID',
  options: {
    toEmail: body.params.customer_email,
    subject: `Your order #${body.params.order_id} is confirmed`,
    emailFormat: 'both',
    text: `Hi ${body.params.name}, your order has been confirmed.`,
    html: `<p>Hi ${body.params.name},</p><p>Your order <strong>#${body.params.order_id}</strong> is confirmed.</p>`,
    attachments: [
      {
        fileUrl: body.params.invoice_url,
        fileName: 'invoice.pdf'
      }
    ]
  }
});

// result.messageId — SMTP message ID
// result.accepted — array of accepted addresses
// result.rejected — array of rejected addresses
```

***

## Shopify

The Shopify actions below connect to a Shopify store via a saved Shopify credential. All actions support `credentialId` plus an `options` object.

**Compatible credentials:** Shopify API Key, Shopify Access Token, Shopify OAuth2

### Shopify Create Order

Create a new order in Shopify. Requires at least one line item. Supports billing/shipping addresses, discount codes, fulfillment options, and email notifications.

| Parameter                        | Required | Description                                                                                                    |
| -------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `credentialId`                   | Yes      | Credential ID for the Shopify store                                                                            |
| `options.lineItems`              | Yes      | Array of line items. Each item requires `quantity`; `variantId`, `productId`, `title`, or `price` are optional |
| `options.email`                  | No       | Customer email address                                                                                         |
| `options.fulfillmentStatus`      | No       | `fulfilled`, `null`, `partial`, or `restocked`                                                                 |
| `options.inventoryBehaviour`     | No       | `bypass`, `decrementIgnoringPolicy`, or `decrementObeyingPolicy`                                               |
| `options.locationId`             | No       | ID of the location that processed the order                                                                    |
| `options.note`                   | No       | Order note                                                                                                     |
| `options.sendFulfillmentReceipt` | No       | Send a shipping confirmation email                                                                             |
| `options.sendReceipt`            | No       | Send an order confirmation email                                                                               |
| `options.sourceName`             | No       | Source identifier, e.g. `web`                                                                                  |
| `options.tags`                   | No       | Comma-separated tags                                                                                           |
| `options.test`                   | No       | Mark as a test order. Default `true`                                                                           |
| `options.billingAddress`         | No       | Billing address object                                                                                         |
| `options.shippingAddress`        | No       | Shipping address object                                                                                        |
| `options.discountCodes`          | No       | Array of `{ amount, code, type }` discounts                                                                    |

```javascript theme={null}
const order = await subverseActions.shopify.createOrder({
  credentialId: 'CREDENTIAL_ID',
  options: {
    lineItems: [{ variantId: body.params.variant_id, quantity: 1 }],
    email: body.params.email,
    shippingAddress: {
      firstName: body.params.first_name,
      lastName: body.params.last_name,
      city: body.params.city,
      country: body.params.country
    },
    sendReceipt: true
  }
});

// order.id — new Shopify order ID
```

### Shopify Get Order

Retrieve a single Shopify order by its ID.

| Parameter         | Required | Description                                       |
| ----------------- | -------- | ------------------------------------------------- |
| `credentialId`    | Yes      | Credential ID for the Shopify store               |
| `options.orderId` | Yes      | Numeric ID of the Shopify order                   |
| `options.fields`  | No       | Comma-separated fields to include in the response |

```javascript theme={null}
const order = await subverseActions.shopify.getOrder({
  credentialId: 'CREDENTIAL_ID',
  options: {
    orderId: body.params.order_id,
    fields: 'id,email,financial_status'
  }
});
```

### Shopify Get All Orders

Retrieve a list of Shopify orders with optional filtering and pagination.

| Parameter                                           | Required | Description                                    |
| --------------------------------------------------- | -------- | ---------------------------------------------- |
| `credentialId`                                      | Yes      | Credential ID for the Shopify store            |
| `options.returnAll`                                 | No       | Auto-paginate to fetch all matching orders     |
| `options.limit`                                     | No       | Max orders to return (default `50`, max `250`) |
| `options.status`                                    | No       | `open`, `closed`, `cancelled`, or `any`        |
| `options.financialStatus`                           | No       | e.g. `paid`, `pending`, `refunded`             |
| `options.fulfillmentStatus`                         | No       | e.g. `shipped`, `unshipped`, `partial`         |
| `options.createdAtMin` / `options.createdAtMax`     | No       | ISO 8601 datetime range for created at         |
| `options.updatedAtMin` / `options.updatedAtMax`     | No       | ISO 8601 datetime range for updated at         |
| `options.processedAtMin` / `options.processedAtMax` | No       | ISO 8601 datetime range for processed at       |
| `options.sinceId`                                   | No       | Return only orders after this ID               |
| `options.ids`                                       | No       | Comma-separated order IDs                      |
| `options.attributionAppId`                          | No       | Filter by attribution app ID                   |
| `options.fields`                                    | No       | Comma-separated fields to include              |

```javascript theme={null}
const orders = await subverseActions.shopify.getAllOrders({
  credentialId: 'CREDENTIAL_ID',
  options: {
    status: 'open',
    limit: 50
  }
});

// orders — array of order objects
```

### Shopify Update Order

Update an existing Shopify order by ID.

| Parameter                 | Required | Description                                  |
| ------------------------- | -------- | -------------------------------------------- |
| `credentialId`            | Yes      | Credential ID for the Shopify store          |
| `options.orderId`         | Yes      | Numeric ID of the order to update            |
| `options.email`           | No       | Updated customer email                       |
| `options.note`            | No       | Internal note                                |
| `options.tags`            | No       | Comma-separated tags replacing existing tags |
| `options.sourceName`      | No       | Source identifier                            |
| `options.locationId`      | No       | Location ID                                  |
| `options.shippingAddress` | No       | Updated shipping address                     |

```javascript theme={null}
const updated = await subverseActions.shopify.updateOrder({
  credentialId: 'CREDENTIAL_ID',
  options: {
    orderId: body.params.order_id,
    note: 'Customer requested expedited handling',
    tags: 'vip,priority'
  }
});
```

### Shopify Delete Order

Permanently delete a Shopify order. Only test orders can be deleted via the API.

| Parameter         | Required | Description                         |
| ----------------- | -------- | ----------------------------------- |
| `credentialId`    | Yes      | Credential ID for the Shopify store |
| `options.orderId` | Yes      | Numeric ID of the order to delete   |

```javascript theme={null}
const result = await subverseActions.shopify.deleteOrder({
  credentialId: 'CREDENTIAL_ID',
  options: {
    orderId: body.params.order_id
  }
});

// result.success — true on success
```

### Shopify Create Product

Create a new product in your Shopify store. A title is required.

| Parameter                | Required | Description                                                |
| ------------------------ | -------- | ---------------------------------------------------------- |
| `credentialId`           | Yes      | Credential ID for the Shopify store                        |
| `options.title`          | Yes      | Product name                                               |
| `options.bodyHtml`       | No       | HTML description                                           |
| `options.handle`         | No       | URL-friendly handle                                        |
| `options.productType`    | No       | Product type category                                      |
| `options.publishedAt`    | No       | ISO 8601 datetime to publish. Set `null` to unpublish      |
| `options.publishedScope` | No       | `global` (Online Store + POS) or `web` (Online Store only) |
| `options.tags`           | No       | Comma-separated tags                                       |
| `options.templateSuffix` | No       | Liquid template suffix                                     |
| `options.vendor`         | No       | Brand or vendor name                                       |

```javascript theme={null}
const product = await subverseActions.shopify.createProduct({
  credentialId: 'CREDENTIAL_ID',
  options: {
    title: body.params.title,
    bodyHtml: body.params.description,
    vendor: body.params.vendor,
    productType: body.params.product_type
  }
});

// product.id — new Shopify product ID
```

### Shopify Get Product

Retrieve a single Shopify product by its ID.

| Parameter           | Required | Description                                       |
| ------------------- | -------- | ------------------------------------------------- |
| `credentialId`      | Yes      | Credential ID for the Shopify store               |
| `options.productId` | Yes      | Numeric ID of the Shopify product                 |
| `options.fields`    | No       | Comma-separated fields to include in the response |

```javascript theme={null}
const product = await subverseActions.shopify.getProduct({
  credentialId: 'CREDENTIAL_ID',
  options: {
    productId: body.params.product_id,
    fields: 'id,title,variants'
  }
});
```

### Shopify Get All Products

Retrieve a list of Shopify products with optional filtering and pagination.

| Parameter                                           | Required | Description                                      |
| --------------------------------------------------- | -------- | ------------------------------------------------ |
| `credentialId`                                      | Yes      | Credential ID for the Shopify store              |
| `options.returnAll`                                 | No       | Auto-paginate to fetch all matching products     |
| `options.limit`                                     | No       | Max products to return (default `50`, max `250`) |
| `options.title`                                     | No       | Filter by exact title                            |
| `options.vendor`                                    | No       | Filter by vendor                                 |
| `options.handle`                                    | No       | Filter by handle                                 |
| `options.productType`                               | No       | Filter by product type                           |
| `options.status`                                    | No       | `active`, `archived`, or `draft`                 |
| `options.publishedStatus`                           | No       | `published`, `unpublished`, or `any`             |
| `options.ids`                                       | No       | Comma-separated product IDs                      |
| `options.sinceId`                                   | No       | Return only products after this ID               |
| `options.createdAtMin` / `options.createdAtMax`     | No       | ISO 8601 datetime range                          |
| `options.updatedAtMin` / `options.updatedAtMax`     | No       | ISO 8601 datetime range                          |
| `options.publishedAtMin` / `options.publishedAtMax` | No       | ISO 8601 datetime range                          |
| `options.fields`                                    | No       | Comma-separated fields to include                |

```javascript theme={null}
const products = await subverseActions.shopify.getAllProducts({
  credentialId: 'CREDENTIAL_ID',
  options: {
    vendor: body.params.vendor,
    status: 'active',
    limit: 50
  }
});

// products — array of product objects
```

### Shopify Update Product

Update an existing Shopify product by ID.

| Parameter                | Required | Description                                |
| ------------------------ | -------- | ------------------------------------------ |
| `credentialId`           | Yes      | Credential ID for the Shopify store        |
| `options.productId`      | Yes      | Numeric ID of the product to update        |
| `options.title`          | No       | Updated product title                      |
| `options.bodyHtml`       | No       | Updated HTML description                   |
| `options.handle`         | No       | Updated URL-friendly handle                |
| `options.productType`    | No       | Updated product type                       |
| `options.publishedAt`    | No       | ISO 8601 datetime. Set `null` to unpublish |
| `options.publishedScope` | No       | `global` or `web`                          |
| `options.tags`           | No       | Updated comma-separated tags               |
| `options.templateSuffix` | No       | Updated Liquid template suffix             |
| `options.vendor`         | No       | Updated vendor name                        |

```javascript theme={null}
const updated = await subverseActions.shopify.updateProduct({
  credentialId: 'CREDENTIAL_ID',
  options: {
    productId: body.params.product_id,
    title: body.params.title,
    tags: body.params.tags
  }
});
```

### Shopify Delete Product

Permanently delete a Shopify product by its ID. This cannot be undone.

| Parameter           | Required | Description                         |
| ------------------- | -------- | ----------------------------------- |
| `credentialId`      | Yes      | Credential ID for the Shopify store |
| `options.productId` | Yes      | Numeric ID of the product to delete |

```javascript theme={null}
const result = await subverseActions.shopify.deleteProduct({
  credentialId: 'CREDENTIAL_ID',
  options: {
    productId: body.params.product_id
  }
});

// result.success — true on success
```

***

## WhatsApp

The WhatsApp actions let you send messages directly through the Meta WhatsApp Business Cloud API using a saved **WhatsApp API** credential. All three actions require a `credentialId`, the recipient's phone number, and your sender's **Phone Number ID** (not stored in the credential — supply it explicitly in `options`).

**Compatible credentials:** [WhatsApp API](/credentials/types/whatsapp-api) (`whatsAppApi`, with Access Token and Business Account ID)

### WhatsApp Send Text Message

Send a plain text message to a WhatsApp number.

| Parameter               | Required | Description                                                   |
| ----------------------- | -------- | ------------------------------------------------------------- |
| `credentialId`          | Yes      | WhatsApp credential ID                                        |
| `options.phoneNumberId` | Yes      | Your WhatsApp Business sender phone number ID                 |
| `options.to`            | Yes      | Recipient phone number in E.164 format — e.g. `+919876543210` |
| `options.message`       | Yes      | The text content to send                                      |
| `options.previewUrl`    | No       | Show a URL preview inside the message. Default `false`        |

```javascript theme={null}
const result = await subverseActions.whatsapp.sendTextMessage({
  credentialId: 'CREDENTIAL_ID',
  options: {
    phoneNumberId: '1234567890',
    to: body.params.customer_number,
    message: `Hi ${body.params.name}, your booking is confirmed for ${body.params.date}.`
  }
});

// result.messages[0].id — WhatsApp message ID
// result.contacts[0].wa_id — recipient's WhatsApp ID
```

### WhatsApp Send Template Message

Send an approved WhatsApp message template. Templates must be created and approved in your Meta Business account before use.

| Parameter               | Required | Description                                                                                                                            |
| ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `credentialId`          | Yes      | WhatsApp credential ID                                                                                                                 |
| `options.phoneNumberId` | Yes      | Your WhatsApp Business sender phone number ID                                                                                          |
| `options.to`            | Yes      | Recipient phone number in E.164 format                                                                                                 |
| `options.templateName`  | Yes      | Exact name of the approved template                                                                                                    |
| `options.languageCode`  | Yes      | Template language code — e.g. `en_US`                                                                                                  |
| `options.components`    | No       | Array of component objects to fill template variables. Each object has a `type` (`header`, `body`, or `button`) and `parameters` array |

```javascript theme={null}
const result = await subverseActions.whatsapp.sendTemplateMessage({
  credentialId: 'CREDENTIAL_ID',
  options: {
    phoneNumberId: '1234567890',
    to: body.params.customer_number,
    templateName: 'order_shipped',
    languageCode: 'en_US',
    components: [
      {
        type: 'body',
        parameters: [
          { type: 'text', text: body.params.order_id },
          { type: 'text', text: body.params.tracking_number }
        ]
      }
    ]
  }
});

// result.messages[0].id — WhatsApp message ID
```

### WhatsApp Send Media Message

Send an image, video, audio file, document, or sticker to a WhatsApp number.

| Parameter               | Required | Description                                                                             |
| ----------------------- | -------- | --------------------------------------------------------------------------------------- |
| `credentialId`          | Yes      | WhatsApp credential ID                                                                  |
| `options.phoneNumberId` | Yes      | Your WhatsApp Business sender phone number ID                                           |
| `options.to`            | Yes      | Recipient phone number in E.164 format                                                  |
| `options.mediaType`     | Yes      | Type of media: `image`, `video`, `audio`, `document`, or `sticker`                      |
| `options.mediaUrl`      | One of   | Public URL of the media file                                                            |
| `options.mediaId`       | One of   | WhatsApp media ID of a previously uploaded file. Provide either `mediaUrl` or `mediaId` |
| `options.caption`       | No       | Caption text shown with the media (supported for `image`, `video`, `document`)          |
| `options.filename`      | No       | Display filename shown to the recipient (documents only)                                |

```javascript theme={null}
const result = await subverseActions.whatsapp.sendMediaMessage({
  credentialId: 'CREDENTIAL_ID',
  options: {
    phoneNumberId: '1234567890',
    to: body.params.customer_number,
    mediaType: 'document',
    mediaUrl: body.params.invoice_url,
    caption: `Invoice for order #${body.params.order_id}`,
    filename: `invoice-${body.params.order_id}.pdf`
  }
});

// result.messages[0].id — WhatsApp message ID
```

***

## Freshdesk

The Freshdesk actions connect to your Freshdesk support desk using a saved **Freshdesk API** credential. All actions require a `credentialId` plus an `options` object.

**Compatible credentials:** [Freshdesk API](/credentials/types/freshdesk-api)

### Freshdesk Create Ticket

Create a new support ticket in Freshdesk. Requires a requester identifier and value.

| Parameter                              | Required | Description                                                                                                                 |
| -------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `credentialId`                         | Yes      | Freshdesk API credential ID                                                                                                 |
| `options.requester`                    | No       | Requester identifier: `email`, `facebookId`, `phone`, `requesterId`, `twitterId`, `uniqueExternalId`. Default `requesterId` |
| `options.requesterIdentificationValue` | Yes      | Value for the selected requester identifier                                                                                 |
| `options.status`                       | No       | `open`, `pending`, `resolved`, or `closed`. Default `open`                                                                  |
| `options.priority`                     | No       | `low`, `medium`, `high`, or `urgent`. Default `low`                                                                         |
| `options.source`                       | No       | `chat`, `email`, `feedbackWidget`, `mobileHelp`, `OutboundEmail`, `phone`, or `portal`. Default `portal`                    |
| `options.name`                         | No       | Name of the requester                                                                                                       |
| `options.subject`                      | No       | Subject of the ticket                                                                                                       |
| `options.description`                  | No       | HTML content of the ticket                                                                                                  |
| `options.type`                         | No       | Ticket type, e.g. `Question`, `Incident`, `Problem`, `Feature Request`, `Refund`                                            |
| `options.agent`                        | No       | Agent ID to assign the ticket to                                                                                            |
| `options.company`                      | No       | Company ID of the requester                                                                                                 |
| `options.product`                      | No       | Product ID associated with the ticket                                                                                       |
| `options.group`                        | No       | Group ID to assign the ticket to                                                                                            |
| `options.ccEmails`                     | No       | Comma-separated CC email addresses                                                                                          |
| `options.tags`                         | No       | Comma-separated tags                                                                                                        |
| `options.dueBy`                        | No       | ISO 8601 datetime when the ticket is due                                                                                    |
| `options.frDueBy`                      | No       | ISO 8601 datetime when the first response is due                                                                            |
| `options.emailConfigId`                | No       | Email config ID                                                                                                             |

```javascript theme={null}
const ticket = await subverseActions.freshdesk.createTicket({
  credentialId: 'CREDENTIAL_ID',
  options: {
    requester: 'email',
    requesterIdentificationValue: body.params.customer_email,
    subject: `Support request from ${body.params.customer_name}`,
    description: `<p>${body.params.issue_description}</p>`,
    priority: 'high',
    source: 'phone',
    type: 'Incident'
  }
});

// ticket.id — new Freshdesk ticket ID
```

### Freshdesk Get Ticket

Retrieve a single Freshdesk ticket by ID.

| Parameter          | Required | Description                  |
| ------------------ | -------- | ---------------------------- |
| `credentialId`     | Yes      | Freshdesk API credential ID  |
| `options.ticketId` | Yes      | ID of the ticket to retrieve |

```javascript theme={null}
const ticket = await subverseActions.freshdesk.getTicket({
  credentialId: 'CREDENTIAL_ID',
  options: {
    ticketId: body.params.ticket_id
  }
});
```

### Freshdesk Get All Tickets

List Freshdesk tickets with optional filters and pagination.

| Parameter                | Required | Description                                                                      |
| ------------------------ | -------- | -------------------------------------------------------------------------------- |
| `credentialId`           | Yes      | Freshdesk API credential ID                                                      |
| `options.returnAll`      | No       | Auto-paginate to fetch all matching tickets                                      |
| `options.limit`          | No       | Max tickets to return (default `5`, max `100`)                                   |
| `options.companyId`      | No       | Filter by company ID                                                             |
| `options.include`        | No       | Array of related data to include: `company`, `description`, `requester`, `stats` |
| `options.order`          | No       | Sort order: `asc` or `desc`. Default `desc`                                      |
| `options.orderBy`        | No       | Sort by `createdAt`, `dueBy`, or `updatedAt`                                     |
| `options.requesterEmail` | No       | Filter by requester email address                                                |
| `options.requesterId`    | No       | Filter by requester ID                                                           |
| `options.updatedSince`   | No       | ISO 8601 timestamp — tickets updated after this time                             |

```javascript theme={null}
const tickets = await subverseActions.freshdesk.getAllTickets({
  credentialId: 'CREDENTIAL_ID',
  options: {
    requesterEmail: body.params.customer_email,
    limit: 10
  }
});

// tickets — array of ticket objects
```

### Freshdesk Update Ticket

Update an existing Freshdesk ticket by ID. Only provided fields are changed.

| Parameter                              | Required | Description                                                                            |
| -------------------------------------- | -------- | -------------------------------------------------------------------------------------- |
| `credentialId`                         | Yes      | Freshdesk API credential ID                                                            |
| `options.ticketId`                     | Yes      | ID of the ticket to update                                                             |
| `options.requester`                    | No       | Requester identifier. If provided, `requesterIdentificationValue` is also required     |
| `options.requesterIdentificationValue` | No       | Required when `requester` is provided                                                  |
| `options.status`                       | No       | `open`, `pending`, `resolved`, or `closed`                                             |
| `options.priority`                     | No       | `low`, `medium`, `high`, or `urgent`                                                   |
| `options.source`                       | No       | `chat`, `email`, `feedbackWidget`, `mobileHelp`, `OutboundEmail`, `phone`, or `portal` |
| `options.name`                         | No       | Name of the requester                                                                  |
| `options.subject`                      | No       | Subject of the ticket                                                                  |
| `options.description`                  | No       | HTML content of the ticket                                                             |
| `options.type`                         | No       | Ticket type                                                                            |
| `options.agent`                        | No       | Agent ID to assign the ticket to                                                       |
| `options.company`                      | No       | Company ID of the requester                                                            |
| `options.product`                      | No       | Product ID associated with the ticket                                                  |
| `options.group`                        | No       | Group ID to assign the ticket to                                                       |
| `options.ccEmails`                     | No       | Comma-separated CC email addresses                                                     |
| `options.tags`                         | No       | Comma-separated tags                                                                   |
| `options.dueBy`                        | No       | ISO 8601 datetime when the ticket is due                                               |
| `options.frDueBy`                      | No       | ISO 8601 datetime when the first response is due                                       |
| `options.emailConfigId`                | No       | Email config ID                                                                        |

```javascript theme={null}
const updated = await subverseActions.freshdesk.updateTicket({
  credentialId: 'CREDENTIAL_ID',
  options: {
    ticketId: body.params.ticket_id,
    status: 'resolved',
    priority: 'medium'
  }
});
```

### Freshdesk Delete Ticket

Delete a Freshdesk ticket by ID.

| Parameter          | Required | Description                 |
| ------------------ | -------- | --------------------------- |
| `credentialId`     | Yes      | Freshdesk API credential ID |
| `options.ticketId` | Yes      | ID of the ticket to delete  |

```javascript theme={null}
const result = await subverseActions.freshdesk.deleteTicket({
  credentialId: 'CREDENTIAL_ID',
  options: {
    ticketId: body.params.ticket_id
  }
});

// result.success — true on success
```

### Freshdesk Add Ticket Note

Add a reply or private note to a Freshdesk ticket.

| Parameter              | Required | Description                                                                     |
| ---------------------- | -------- | ------------------------------------------------------------------------------- |
| `credentialId`         | Yes      | Freshdesk API credential ID                                                     |
| `options.ticketId`     | Yes      | ID of the ticket to add the note to                                             |
| `options.noteBody`     | Yes      | Content of the note or reply                                                    |
| `options.private`      | No       | If `true`, the note is private (internal). Default `false`                      |
| `options.notifyEmails` | No       | Comma-separated email addresses to notify                                       |
| `options.incoming`     | No       | Whether the note appears as created from outside the web portal. Default `true` |

```javascript theme={null}
const note = await subverseActions.freshdesk.addTicketNote({
  credentialId: 'CREDENTIAL_ID',
  options: {
    ticketId: body.params.ticket_id,
    noteBody: `Customer called about ${body.params.issue}. Escalated to L2.`,
    private: true
  }
});

// note.id — new note ID
```

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Custom Functions" icon="code" href="/integrations/agentic-functions/custom-functions">
    Write and test custom function code using these actions
  </Card>

  <Card title="Credentials" icon="key" href="/credentials/overview">
    Set up and manage the credentials used by your actions
  </Card>
</CardGroup>
