Read and write files in the user’s Google Drive or OneDrive, build reports as Sheets and Docs, save pages to Notion, and send designs to and from Canva.
Google Sheets: export structured data to a spreadsheet the a…
create_sheet#
Create a Google Sheet
Create a new Google Spreadsheet in the user’s Drive and optionally fill it with rows — e.g. export a swipefile, ad list, or performance report. Pass rows as an array of row arrays (first row = headers). Returns the spreadsheet id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).
| Parameter | Type | Description |
|---|
title | string | spreadsheet title |
rows | string | number | boolean[][] | rows to write — array of row arrays; first row = headers |
append_to_sheet#
Append rows to a Google Sheet
Append rows to a Google Sheet Hermoso can reach — one it created (pass the spreadsheetId from create_sheet) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). rows = array of row arrays.
| Parameter | Type | Description |
|---|
spreadsheetIdrequired | string | the spreadsheet id from create_sheet |
rowsrequired | string | number | boolean[][] | rows to append — array of row arrays |
range | string | range to append at (default A1 / first sheet) |
read_sheet#
Read a Google Sheet range read-only
Read cells from a Google Sheet Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING spreadsheet becomes readable; find its id with list_drive_files). Pass the spreadsheetId (from create_sheet) OR paste a Google Sheets URL as sheetUrl. If Google answers that the file was not found, the user has not picked it yet — ask them to pick it in the app rather than retrying. Returns a 2-D array of values.
| Parameter | Type | Description |
|---|
spreadsheetId | string | the spreadsheet id (from create_sheet) |
sheetUrl | string | a Google Sheets URL to read — the spreadsheet id is extracted from it |
range | string | A1 range, e.g. "A1:D50" (default A1:Z1000) |
Notion
list_notion_pages#
List Notion pages read-only
Find the Notion pages and databases the user shared with Hermoso, by title, newest edited first. Gives the page id that save_to_notion, append_to_notion_page and read_notion_page take, or the database id to save rows into. Read-only, free. Needs Notion connected (Settings > Connectors > Notion).
| Parameter | Type | Description |
|---|
query | string | words in the title |
type | "page" | "database" | limit results to pages or to databases; omit for both |
limit | number | 1-100, default 25 |
cursor | string | from the previous page |
save_to_notion#
Save to Notion
Save to the user's Notion as a NEW page: a research report, competitor teardown or campaign report as `content` (markdown: headings, lists, links, and an image or video URL on its own line becomes the media), a swipefile collection (swipefileCollection: name or id; each ad with its creative, copy, run dates and link), the content calendar (includeSchedule:true), or a mix. Put it under parentPageId, as a row in databaseId, or omit both for a private page in their workspace. Long text is split across blocks, never cut. The answer is read back from Notion. Free; never overwrites anything.
| Parameter | Type | Description |
|---|
title | string | title of the new Notion page |
content | string | markdown body |
swipefileCollection | string | swipefile collection name or id |
includeSchedule | boolean | add the scheduled posts |
parentPageId | string | page id or link to save under |
databaseId | string | database id or link: saves as a new row |
dataSourceId | string | data source id to save into as a new row; pass only one of parentPageId, databaseId, dataSourceId |
icon | string | one emoji |
append_to_notion_page#
Add to a Notion page
Add to the END of an existing Notion page (pageId or its link, from list_notion_pages): markdown content, a swipefileCollection, or includeSchedule:true. Nothing already on the page changes. Free.
| Parameter | Type | Description |
|---|
pageIdrequired | string | page id or link |
content | string | markdown to add |
swipefileCollection | string | swipefile collection name or id to append, with each ad's creative, copy, dates and link |
includeSchedule | boolean | true appends the scheduled posts too |
read_notion_page#
Read a Notion page read-only
Read a Notion page the user shared with Hermoso, as markdown (pageId or a Notion link). A database id answers with its data sources instead. Read-only, free.
| Parameter | Type | Description |
|---|
pageIdrequired | string | page id or Notion link |
export_schedule_to_notion#
Export the schedule to Notion
Export the posting schedule to a Notion database, one row per post: date, channels, caption, media link and status. Creates a new database (under parentPageId, or private in the workspace) or writes into an existing one (databaseId), matching its columns by name. historyDays adds posts already published. Up to 100 rows per run; the answer says if more were left. Free.
| Parameter | Type | Description |
|---|
parentPageId | string | Notion page to create the new database under; omit for a private page in the workspace |
databaseId | string | an existing database id or link |
dataSourceId | string | data source id of an existing database to write rows into, matched to columns by name |
title | string | name for a new database |
historyDays | number | 0-90 days of published posts to include |
query_notion_database#
Read a Notion database read-only
Read the ROWS of a Notion database the user shared with Hermoso — e.g. the team's content calendar: each row's title, id, link and every property as a plain value (status, date, channels, caption, links), plus the database's columns with their types and options. Filter with match {"Status": "Scheduled"} (typed from the database's own columns) or a raw Notion filter object; sort with sortBy (a column, or created_time / last_edited_time) and direction. Up to 100 rows per call, cursor for more; the answer says when Notion stopped at its 10,000-result limit. Use it to import a calendar and schedule its posts, or to find the row update_notion_row changes. Read-only, free. Needs Notion connected (Settings > Connectors > Notion).
| Parameter | Type | Description |
|---|
databaseId | string | database id or link (list_notion_pages type:"database") |
dataSourceId | string | data source id from an earlier read, as an alternative to databaseId |
match | object | {"Column": value} — equality for status/select/checkbox/number/date, contains for text/multi-select |
filter | object | a raw Notion filter object |
sortBy | string | column name to sort by, or created_time or last_edited_time |
direction | "ascending" | "descending" | ascending (default) or descending |
limit | number | 1-100, default 50 |
cursor | string | nextCursor from the previous page of rows |
archived | boolean | read archived rows instead |
update_notion_row#
Update a Notion database row destructive
Change properties on ONE row of a Notion database (pageId from query_notion_database) — e.g. mark a calendar row Status "Posted" and put the live post link in its URL column. properties is {"Column": value} by column name: text, number, checkbox, select (an existing option or a new one), status (must be an existing option), multi-select (array; replaces the whole value), date (ISO, or {start, end}), url, email, phone; null clears. Validated against the database's own columns before anything is sent and refused by name otherwise, never coerced; columns you do not name keep their values. Read back from Notion. trash:true moves the row to Notion's trash (restorable; returns a preview first and needs confirm:true), trash:false restores it. Free.
| Parameter | Type | Description |
|---|
pageIdrequired | string | the row id or link |
properties | object | {"Column": value} |
trash | boolean | true moves the row to Notion trash (needs confirm); false restores it |
confirm | boolean | true confirms moving the row to trash; not needed for other changes |
Google Docs: export copy / brief / report as a doc the app c…
create_doc#
Create a Google Doc
Create a new Google Doc in the user’s Drive with a title + optional body text — e.g. export ad copy, a creative brief, or a report. Returns the document id + URL. Needs Google Drive connected (Settings > Connectors > Google Drive — one connection covers Drive, Sheets and Docs).
| Parameter | Type | Description |
|---|
title | string | document title |
text | string | body text to insert |
append_to_doc#
Append text to a Google Doc
Append text to the end of a Google Doc Hermoso can reach — one it created (pass the documentId from create_doc) or one the user handed over with the Google file picker in the app (find its id with list_drive_files). Optional `dropdown:{title, options:[2-50], selected}` appends a Google Docs dropdown chip after the text — e.g. text "Status: " + dropdown {title:"Status", options:["Draft","In review","Approved"]} gives a brief a status the team can click to change; the chip is confirmed by reading the doc back. Set it later with update_doc dropdowns.
| Parameter | Type | Description |
|---|
documentIdrequired | string | the document id from create_doc |
text | string | text to append at the end of the doc (optional when a dropdown is passed) |
dropdown | object | a dropdown chip to append after the text |
read_doc#
Read a Google Doc read-only
Read the text of a Google Doc Hermoso can reach — one it created, or one the user handed over with the Google file picker in the app (that is how an EXISTING doc becomes readable; find its id with list_drive_files). Pass documentId (from create_doc) OR paste a Google Docs URL as docUrl. Under the drive.file scope it reaches nothing else in the user’s Drive; if Google answers that the file was not found, the user has not picked it yet — ask them to pick it in the app rather than retrying. Returns the plain text of EVERY tab (each headed by its tab name when there are several), with smart chips (dropdowns, people, dates, rich links) shown as the text Docs displays, plus `dropdowns[]` — each chip with its title, current value and options, which is what update_doc dropdowns sets. Read-only, free.
| Parameter | Type | Description |
|---|
documentId | string | the document id (from create_doc) |
docUrl | string | a Google Docs URL to read — the document id is extracted from it |
The Sheets / Docs WRITE surface
list_sheet_tabs#
List the tabs in a Google Sheet read-only
The tabs in a Google Spreadsheet, each with its name, numeric sheetId, row/column count and position. Call this BEFORE naming a tab in update_sheet / clear_sheet_range / manage_sheet_tabs / format_sheet, and before proposing to delete one — it is how you learn what the file actually contains instead of guessing at a name. Read-only, free.
| Parameter | Type | Description |
|---|
spreadsheetId | string | the spreadsheet id (from create_sheet, or list_drive_files for one the user picked) |
sheetUrl | string | a Google Sheets URL — the id is extracted from it |
update_sheet#
Write to a range in a Google Sheet destructive
CORRECT cells in a Google Sheet — write values to an exact range, overwriting whatever is there. This is the fix append_to_sheet cannot make: appending only ever adds rows at the bottom, so without this a wrong number stays wrong forever and the only "correction" is a second row contradicting the first. Pass `range` (e.g. "B2:C5", or "Q3 Report!B2" to name a tab — list_sheet_tabs gives the names) and `values` as an array of row arrays; an anchor cell like "B2" is fine and the block is written down and right from it. Writing into EMPTY cells goes straight through. Writing OVER cells that already hold values is REFUSED first, naming exactly how many filled cells would be overwritten — show the user that, get a yes, then call again with confirm:true. The result is READ BACK from the sheet, so what you report is what the sheet now holds rather than what Google accepted.
| Parameter | Type | Description |
|---|
spreadsheetId | string | Google spreadsheet id; use this or sheetUrl |
sheetUrl | string | Google Sheets link, instead of spreadsheetId |
range | string | A1 range or anchor cell, e.g. "B2:C5", "B2", or "Q3 Report!B2" (default A1) |
values | string | number | boolean[][] | array of row arrays to write |
updates | object[] | write SEVERAL disjoint ranges in one call, instead of range+values |
valueInputOption | "USER_ENTERED" | "RAW" | USER_ENTERED (default) parses formulas, dates and numbers the way typing them would; RAW stores every value as literal text |
confirm | boolean | required only when the target range already holds values |
clear_sheet_range#
Clear a range in a Google Sheet destructive
Empty a range of cells in a Google Sheet, leaving the rows themselves in place. DESTRUCTIVE: call it WITHOUT confirm first and nothing is cleared — you get back the real number of filled cells in that exact range. Show the user that number, get an unambiguous yes, then call again with confirm:true AND confirmCells set to it. The echo is not ceremony: it is what catches naming A1:Z1000 when you meant A1:Z10, which is the mistake that actually happens. There is deliberately NO default range. The clear is read back and reported as confirmed only if the range really is empty afterwards. To remove a whole tab instead, use manage_sheet_tabs.
| Parameter | Type | Description |
|---|
spreadsheetId | string | Google spreadsheet id; use this or sheetUrl |
sheetUrl | string | Google Sheets link, instead of spreadsheetId |
rangerequired | string | the range to clear, e.g. "A2:D50" or "Sheet1!A2:D50" |
confirm | boolean | true clears the range; call without it first to get the filled-cell count |
confirmCells | number | echo back the filled-cell count the unconfirmed call reported |
manage_sheet_tabs#
Add, rename or delete a sheet tab destructive
Add, rename or delete a tab in a Google Spreadsheet. action:"add" + title · action:"rename" + tab + newTitle · action:"delete" + tab. Name the tab by its TITLE or its numeric sheetId (list_sheet_tabs gives both); an unknown tab is refused with the real list rather than a Google error nobody can map back. DELETING a tab destroys everything on it: call it without confirm first to get the filled-cell count, then confirm:true + confirmCells. Google does not allow removing the LAST remaining tab in a file, and that is refused by name with the way out (clear it, or delete the whole file with delete_drive_file). Every action is read back from the spreadsheet before it is reported as done.
| Parameter | Type | Description |
|---|
spreadsheetId | string | Google spreadsheet id; use this or sheetUrl |
sheetUrl | string | Google Sheets link, instead of spreadsheetId |
actionrequired | "add" | "rename" | "delete" | add, rename or delete a tab |
tab | string | which tab — its title or numeric sheetId (rename / delete) |
title | string | the name for the new tab (action:"add") |
newTitle | string | what to rename the tab to (action:"rename") |
confirm | boolean | true deletes a tab that holds data; needs confirmCells |
confirmCells | number | the filled-cell count an unconfirmed delete reported |
format_sheet#
Format a Google Sheet destructive
Make an exported sheet readable: bold the header row, FREEZE it so it stays visible while scrolling, and auto-size the columns so nothing is cut off. Worth calling right after create_sheet — a raw export with unsized columns and a header that scrolls away is the difference between a spreadsheet someone reads and one they close. Changes no cell VALUE, so it is never gated. The defaults do all three on the first tab; pass `tab` to pick another, freezeRows:0 to skip freezing, boldHeader:false or autoResize:false to skip those.
| Parameter | Type | Description |
|---|
spreadsheetId | string | Google spreadsheet id; use this or sheetUrl |
sheetUrl | string | Google Sheets link, instead of spreadsheetId |
tab | string | tab title or numeric sheetId (default: the first tab) |
boldHeader | boolean | bold the header row; default true, false skips it |
freezeRows | number | how many top rows to freeze (default 1, 0 = none) |
autoResize | boolean | auto-size columns to fit their content; default true, false skips it |
update_doc#
Edit a Google Doc in place destructive
EDIT a Google Doc — the correction append_to_doc cannot make. Two shapes: `replacements:[{find, replace}]` rewrites specific text wherever it appears (call read_doc first and match the text EXACTLY; matchCase:false ignores case), or `rewrite:"…"` replaces the ENTIRE body (rewrite:"" empties it), or `dropdowns:[{title, value}]` sets a dropdown chip (e.g. Status -> Approved — read_doc lists every chip with its options; pass dropdownId when two share a title; an unknown title or option is refused with the real list and nothing changes); the same entry with options:[…] (the COMPLETE new list, 2–50) and/or newTitle changes the dropdown itself for every chip built from it, and an option you drop that a chip shows needs replace:{"Old":"New"}. Find/replace runs immediately and REPORTS how many occurrences changed — zero matches is reported as a FAILURE to match, never as a quiet success, because a text edit that silently does nothing is worse than one that visibly fails. A whole-body rewrite is destructive: call it without confirm first to get the character count, then confirm:true + confirmCells. Both are index-free by design — an agent cannot reliably compute Google’s character offsets, and a wrong offset deletes the wrong sentence.
| Parameter | Type | Description |
|---|
documentId | string | the document id (from create_doc, or list_drive_files for one the user picked) |
docUrl | string | a Google Docs URL — the id is extracted from it |
replacements | object[] | find/replace pairs, applied in order |
rewrite | string | replace the WHOLE body with this text ("" empties the doc) |
confirm | boolean | true runs a whole-body rewrite after the character count is shown |
confirmCells | number | echo back the character count the unconfirmed call reported (rewrite only) |
dropdowns | object[] | dropdown chips to set, or dropdowns to change |
Microsoft OneDrive: full CRUD over the user's OneDrive
save_to_onedrive#
Save file(s) to OneDrive
Save a Hermoso render — or ANY file — into the user’s connected Microsoft OneDrive. Pass a Hermoso render URL as url (or urls[] for several); for a local/external file, call upload_file first and pass the url it returns. Optional folder (created if new) + name. Returns the OneDrive file(s) with a webViewLink. Needs OneDrive connected (Settings > Connectors > OneDrive).
| Parameter | Type | Description |
|---|
url | string | a single Hermoso render URL to save |
urls | string[] | several render URLs (up to 20) to save in one call |
folder | string | OneDrive folder name to save into (created if new) |
name | string | file name (single save) |
list_onedrive_files#
List OneDrive files read-only
List files & folders in the user’s OneDrive — the root by default, a folder’s contents (folderId), or a name search (query). onlyFolders:true lists folders only. Paginate with pageToken (the cursor from a previous call). Read-only.
| Parameter | Type | Description |
|---|
query | string | search — only items whose name matches this |
folderId | string | list the contents of this folder id |
onlyFolders | boolean | list folders only |
pageSize | number | rows per page (1–200, default 50) |
pageToken | string | cursor from a previous call |
get_onedrive_file#
Get a OneDrive file’s details read-only
Fetch one OneDrive item’s metadata — name, type, size, modified time, a webViewLink to open it and a webContentLink to download it. Pass fileId (from list_onedrive_files). Read-only.
| Parameter | Type | Description |
|---|
fileIdrequired | string | the OneDrive item id (from list_onedrive_files) |
update_onedrive_file#
Rename / move a OneDrive file destructive
Update a OneDrive item: rename (name) and/or move it into a folder (moveToFolderId). Pass fileId (from list_onedrive_files). To remove an item, use delete_onedrive_file.
| Parameter | Type | Description |
|---|
fileIdrequired | string | the OneDrive item id |
name | string | new name |
moveToFolderId | string | folder id to move the item into (from create_onedrive_folder / list_onedrive_files) |
delete_onedrive_file#
Delete a OneDrive file destructive
Delete a OneDrive item — it moves to the OneDrive recycle bin (recoverable there). Pass fileId (from list_onedrive_files) + confirm:true. Confirm the exact file with the user first.
| Parameter | Type | Description |
|---|
fileIdrequired | string | the OneDrive item id |
confirm | boolean | REQUIRED true |
share_onedrive_file#
Share a OneDrive file as a link destructive
Create a SHARING LINK to a OneDrive file or folder (fileId from list_onedrive_files): type view (read-only, default), edit, or embed (an <iframe> link, personal OneDrive only); scope anonymous (anyone with the link, default), organization (work or school OneDrive only) or users (only people you invite); optional expiresAt (date or ISO) and password (on a personal OneDrive both need a Microsoft 365 subscription; the refusal says so). An ANONYMOUS EDIT link lets anyone holding it change or delete the file, so it needs confirm:true after you show the user that; every other link is created straight away. If Hermoso already made the same kind of link, OneDrive returns that one and the reply says it was reused. Read back from the item's permission list. 0 credits. Needs OneDrive connected.
| Parameter | Type | Description |
|---|
fileIdrequired | string | OneDrive file or folder id from list_onedrive_files |
type | "view" | "edit" | "embed" | view (default), edit, or embed (personal OneDrive only) |
scope | "anonymous" | "organization" | "users" | anonymous (default, anyone with the link), organization (work or school), or users (invited people only) |
expiresAt | string | e.g. 2026-11-30 or 2026-11-30T17:00:00Z |
password | string | personal OneDrive with Microsoft 365 |
confirm | boolean | required for an anonymous edit link |
onedrive_file_permissions#
Who can open a OneDrive file destructive
action list (default): every sharing link and person with access to a OneDrive file or folder: link URL, view or edit, scope, expiry, password, whether it is inherited from a parent folder. action remove: revoke one (permissionId from the list); anyone relying on it loses access at once and the link cannot be restored, so WITHOUT confirm:true nothing is removed and you get what would go. An inherited permission is removed on the parent folder, and the owner's own access cannot be removed. Removal is verified by reading the list back. 0 credits. Needs OneDrive connected.
| Parameter | Type | Description |
|---|
fileIdrequired | string | OneDrive file or folder id |
action | "list" | "remove" | list (default) or remove |
permissionId | string | for remove |
confirm | boolean | required true for remove |
convert_onedrive_file#
Convert a OneDrive file to PDF or JPG
Turn a file already in the user’s OneDrive into a PDF or a JPG — Microsoft does the conversion on its own servers, so nothing is re-encoded here and nothing is lost in a screenshot. It reads about 130 source formats, which is the point: PowerPoint and Word decks, Excel, Photoshop PSD, Illustrator AI, Sketch, 3D (fbx/glb/obj), video (mp4/mov/webm), HEIC from an iPhone, and the raw camera formats (CR2, NEF, ARW, DNG) that nothing else in this product can open. Use it to turn a client’s deck into images you can actually put in an ad, to get a usable JPG out of a designer’s PSD or a photographer’s raw file, or to hand someone a PDF of a spreadsheet. CONVERTING TO JPG REQUIRES BOTH width AND height — Microsoft refuses the call without them. The result is stored at a durable Hermoso URL you can pass straight to a render or a post; Microsoft’s own conversion link expires within minutes, so do not hand that one to anyone. Needs OneDrive connected — no new permission.
| Parameter | Type | Description |
|---|
fileIdrequired | string | the OneDrive item id, from list_onedrive_files |
format | "pdf" | "jpg" | default pdf |
width | number | REQUIRED for jpg — output width in pixels |
height | number | REQUIRED for jpg — output height in pixels |
create_onedrive_folder#
Create a OneDrive folder
Create a folder in the user’s OneDrive (optionally nested under parentId) to organize saved files. Returns the folder id + webViewLink. Use that id as update_onedrive_file’s moveToFolderId or as parentId for a nested folder. NOTE: save_to_onedrive’s `folder` is a NAME (find-or-created), not this id.
| Parameter | Type | Description |
|---|
namerequired | string | folder name |
parentId | string | parent folder id for a nested folder (default: OneDrive root) |