Automations API
Account functions, schedules, webhooks and mailhooks, plus their run history and environment variables.
Endpoints
/api/v1/automations/listFunctionsList the current user's account functions (id, name, description, isActive, grants).
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listFunctions \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{}'/api/v1/automations/getFunctionGet one account function by id — its code, grants, summary, isActive, and its `version` (send it as seenVersion when you prepare Full data access for it with setFunctionCapabilities).
Parameters
id | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/getFunction \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example"
}'/api/v1/automations/createFunctionCreate a new account function. `code` runs as a function BODY with `ctx` and `params` in scope — `export default async (params, ctx) => {…}` is also accepted; `import` is not. Params: name, description?, code?, grants?, maxRetries? (retries when run by an automation on failure; 0 = run once (default). Leave at 0 unless the user asks for retries. Only set it for steps that are safe to run twice, set > 0 ONLY if the function is idempotent — safe to re-run without doubling side effects). Returns the created function (with id). Each site in grants.bases can be given as Read or Read and write: { projectId, access: "read" } = Read (the function can look at the site's tables and files but every write is refused: "This function may only read <site>"), { projectId, access: "write" } = Read and write; no access saved counts as Read and write. On the function's Access tab (Site access) a person picks it per site; a newly ticked site starts as Read, so give a site as Read unless the function must change it. ⚠️ Starts isActive:false — call updateFunction {isActive:true} before pointing a mailhook/webhook/schedule at it, or triggers will fail loudly with "target is INACTIVE". POWERS: ctx.email, ctx.env, a site's users / entitlements, and Full data access are refused until a person allows that power (grants.capabilities does nothing): prepare it with setFunctionCapabilities and the person approves the card. Deny by default: a function with no access rule is refused for site users on every route, a page-started run reads only what that person may read (Full data access, and any change to a function that has it, waits for a person to approve its card), and a site function uses only its own site's base. Read getCodeGuide topic "functions" for the full rules. SERENITIES TOOLS: ctx.connections.call('Serenities', tool, args) runs only the tools on its own list — grants.serenitiesTools: ["tables_getRows", …] (its own key, separate from grants.connections), empty by default — and only inside its grants; never automations_invokeFunction / site_invokeBackendFunction (use ctx.functions.run) or a person-only tool (secrets, sign-ins, agents, approvals, installs). You may only list tools the person you act for may use. An AI agent needs "Can create functions" switched on by a person, and its function's grants are copied from the agent. WHO RUNS IT: a function runs as the workspace — whoever builds it decides what it can do and can only wire what they may use (the save is refused otherwise); the person who started it is passed in as data (ctx.run.startedBy), never as rights; a site visitor's function sees only what that visitor may see. There is no "Ask first" inside a function: each call is allowed or refused. Check runner-supplied inputs (which row, which address, which amount) before acting on them.
Parameters
name | stringrequired | |
description | string | |
code | string | |
grants | object | |
accessRules | object | { execute, runBy }. runBy = "Who can run this function": { pages, functions, agents, members? } (function / agent ids, or 'any'; members = which workspace members may run it). Left out: no page and no other function or agent can start it until you pick. |
maxRetries | integer | Retries when run by an automation on failure. 0 = run once (default). Set > 0 ONLY if this function is idempotent (safe to re-run without doubling side effects). Leave at 0 unless the user asks for retries. Only set it for steps that are safe to run twice. |
toolSpec | object | Structured MCP-tool schema { inputSchema, outputSchema?, returns?, sideEffects? } authored WITH the code — describes what this connector takes/returns. |
exposed | boolean | true = the owner's automations & AI may call this as a catalog-listed method (UI: "Automations & AI can use this function"); false (default) = private/internal. |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/createFunction \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "example",
"description": "example",
"code": "example"
}'/api/v1/automations/updateFunctionUpdate an account function — REQUIRED to change its code, grants or accessRules. A function's grants are replaced WHOLE when you send them: read them first (automations_getFunction), add the site to grants.bases (one entry per site it touches, { projectId }), and send the rest unchanged — its connected apps (grants.connections) and Serenities tools (grants.serenitiesTools) included, or they are removed. Each site in grants.bases can be given as Read or Read and write: { projectId, access: "read" } = Read (the function can look at the site's tables and files but every write is refused: "This function may only read <site>"), { projectId, access: "write" } = Read and write; no access saved counts as Read and write. On the function's Access tab (Site access) a person picks it per site; a newly ticked site starts as Read, so give a site as Read unless the function must change it. Params: id, plus any of name/description/summary/code/isActive/timeout/grants/maxRetries (0 = run once. Leave at 0 unless the user asks for retries. Only set it for steps that are safe to run twice)/accessRules ({ execute: <condition> } — who may invoke it from outside). PUBLISHED-APP ACCESS is deny-by-default and needs ALL THREE: grants.bases includes the site, accessRules.execute allows the caller (e.g. { execute: { type: "authenticated" } }; "public" opens it only through the granted sites, an account function has no "Anyone, everywhere" level), and accessRules.runBy.pages is true (the tick "Visitors on the site page (directly)" under "Who can run this function"; off for a new account function); the app then calls functions.invokeAccount("<name>", params) from its frontend. Deny by default: a function with no access rule is refused for site users on every route, a page-started run reads only what that person may read (Full data access, and any change to a function that has it, waits for a person to approve its card), and a site function uses only its own site's base. Read getCodeGuide topic "functions" for the full rules. Mention Full data access only when the owner asks for a page to show more. Account functions use GLOBAL (account-level) env vars — ctx.secrets.ref('NAME') in ctx.fetch (this run's marker; for the hosts on the function's list), ctx.secrets.verifyHmac for a signature, ctx.env.get for a non-secret setting and ctx.env.set for APP_* values use the account store (manage via setEnvVar/listEnvVars/deleteEnvVar). DELIBERATELY SEPARATE from per-site env: site functions read site env, account functions read global env, no fallback between them. ctx.sites is keyed by display NAME, not slug (robust: Object.values(ctx.sites).find(s => s.projectId === id)); methods.call needs exposed:true on the target; when nobody from a site is behind the run it is a service call (no execute rule evaluated), and when a page started the run the method runs as that page's visitor and its own execute rule is judged for them (no rule = refused). ⚠️ New functions start isActive:false — ACTIVATE before pointing a mailhook/webhook/schedule at them. grants REPLACES the saved grants: send them whole (bases, databaseIds, connections, serenitiesTools) or what you leave out is removed. Adding a Serenities tool needs you to be allowed to use it yourself; new code in a function holding a tool you may not use is refused. An AI agent can't change a function's grants or its declared kind (toolSpec.kind), can't change a function that acts as the workspace or uses sites, tables or connections the agent may not use, and can't change the code of a function that holds secrets, powers, connected apps or Serenities tools.
Parameters
id | stringrequired | |
name | string | |
description | string | |
summary | string | |
code | string | |
isActive | boolean | |
timeout | integer | |
maxRetries | integer | Retries when run by an automation on failure. 0 = run once. Set > 0 only if idempotent. Leave at 0 unless the user asks for retries. Only set it for steps that are safe to run twice. |
grants | object | |
accessRules | object | |
toolSpec | object | Structured MCP-tool schema — update TOGETHER with the code so they stay in sync; null removes it. |
exposed | boolean | |
seenUpdatedAt | string | The version (updatedAt) the screen showed: the save is refused when the function changed since, so a stale tab never puts back what another removed. |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/updateFunction \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"name": "example",
"description": "example"
}'/api/v1/automations/publishFunctionPublish an account function's draft: it becomes the live version every run runs from the next run on (a run already going finishes on its own). Params: id, versionId (the draft). While versions are switched on, every save of a function is a draft and nothing runs it until a person publishes it. Only a person who may change functions publishes or rolls back (from the dashboard, their own AI chat or MCP); an AI agent or an automation saves drafts only. On a function with Full data access the answer is a card (pendingChange, approveLink) the person taps; nothing goes live until they do. Answers { versionId, number, publishedBy, publishedAt }. While versions are off it is refused (every save is live at once).
Parameters
id | stringrequired | |
versionId | stringrequired | The draft to make live (its id from the function's versions). |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/publishFunction \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"versionId": "example"
}'/api/v1/automations/rollBackFunctionRoll an account function back: an earlier version that was live before becomes live again (no new version; an open draft is kept). Params: id, versionId (the earlier version). While versions are switched on, every save of a function is a draft and nothing runs it until a person publishes it. Only a person who may change functions publishes or rolls back (from the dashboard, their own AI chat or MCP); an AI agent or an automation saves drafts only. On a function with Full data access the answer is a card (pendingChange, approveLink) the person taps; nothing goes live until they do. Answers { versionId, number, publishedBy, publishedAt }. While versions are off it is refused (every save is live at once).
Parameters
id | stringrequired | |
versionId | stringrequired | The earlier version to make live again (one that was live before). |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/rollBackFunction \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"versionId": "example"
}'/api/v1/automations/listFunctionVersionsList an account function's versions, newest first: the draft (the latest save, waiting for a person to publish it), the live version (what every run runs) and the earlier ones that were live before (roll back to one of them). Params: functionId. Answers { switchedOn, versions: [{ id (the versionId Publish and Roll back take), number, state (draft | live | archived), isLive, writtenBy (a person's name or "AI agent <name>"), createdAt, publishedBy, publishedAt, linesChanged (code lines changed against the version before it) }] } and never the code. Read it before you publish or roll back: publishFunction takes the draft's id, rollBackFunction an archived one's. switchedOn false = versions are not switched on yet, every save is live at once and there is nothing to list.
Parameters
functionId | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listFunctionVersions \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"functionId": "example"
}'/api/v1/automations/deleteFunctionPermanently delete an account function. Params: id.
Parameters
id | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/deleteFunction \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example"
}'/api/v1/automations/setFunctionCapabilitiesPrepare an account function's powers (Full data access "service", "email", "env", "users", "entitlements", "social", "agents"). Params: id, and either capabilities (the whole list to keep) or remove (only these taken away). Giving a power waits for a person: the answer carries pendingChange (a card they tap, its approveLink) and nothing changes until they approve; Full data access is high risk and also needs seenVersion (getFunction's version). Taking Full data access away applies at once with a notice and an Undo. Never say a power is on before the person approved it. Where the "acts as the workspace" switch is available, "service" is refused here (nothing changes, no card): prepare it with setFunctionIdentityMode instead; re-sending or taking away a "service" the function already holds works as before.
Parameters
id | stringrequired | |
capabilities | array | |
remove | array | |
seenVersion | string | The function's version the dashboard showed (getFunction): full data access is given only to that version. |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/setFunctionCapabilities \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"capabilities": {},
"remove": {}
}'/api/v1/automations/setFunctionIdentityModePrepare the "acts as the workspace" switch of an account function. Params: id, mode ("workspace" = when a site page starts it, it reads and changes every row of the sites it was given, ignoring the visitor's data rules; "as_set_up" = it follows them, the default), seenVersion (getFunction's version; required to switch it on). Switching it on is high risk: the answer carries pendingChange (a card, its approveLink) and nothing changes until a person approves it, whoever asks (your own AI included). Only the Owner or an Admin can approve it. Switching it off waits for one tap on its card too. Never say it is on before the person approved it. Use only when the user asks for it; refused where the switch is not available yet.
Parameters
id | stringrequired | |
mode | stringrequired | 'workspace' = it acts as the workspace (ignores a site's data rules for whoever starts it); 'as_set_up' = it follows them (the default). |
seenVersion | string | The function's version the dashboard showed (getFunction): it is switched on only for that version. |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/setFunctionIdentityMode \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"mode": "example",
"seenVersion": "example"
}'/api/v1/automations/setFunctionSecretsPrepare an account function's list of account secrets. Params: id, secrets (the WHOLE list; [] = none) — each { name, hosts (where it may be sent; none = only to check signatures with ctx.secrets.verifyHmac), keyFormat? ({ encoding: utf8|base64|hex, prefix? }, for a signing key) }. The code never sees a value: it uses ctx.secrets.ref(name). A new secret, a new host or a new key format waits for a person: the answer carries pendingChange (a card they tap) and nothing changes until they approve; taking one away applies at once with a notice and an Undo. A list can never send a secret beyond the secret's own allowed hosts; names that are not the account's are ignored when it is applied. Use only when the user asks for it; a standing "keep fixing" approval never covers a new secret, host or key format. An AI agent or an automation is refused: a list is a person's.
Parameters
id | stringrequired | |
secrets | arrayrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/setFunctionSecrets \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"secrets": {}
}'/api/v1/automations/sitesManifestList the account's sites + their tables/fields. Call this for exact site/table/field names before writing code.
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/sitesManifest \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{}'/api/v1/automations/aiAuthorAsk the AI code author to write or change the code of one account function. Params: id (the function's id, from automations_listFunctions), message (what you want, in plain English, up to 4000 characters), currentCode? (the function's code as it is now, from automations_getFunction: send it so the author changes that code instead of starting from nothing). The author knows this workspace's sites and tables and the connections a function can use (only the tools switched on for each) and never sees a sign-in. It returns { reply, code }: a one-line summary and the full new function body. It does NOT save anything: to keep the code, send it to automations_updateFunction, which has its own rules. It spends the workspace's AI credits, one charge per call; when the credits are used up nothing is made, nothing is charged, and the answer says who can add more.
Parameters
id | stringrequired | |
message | stringrequired | |
currentCode | string |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/aiAuthor \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"message": "example",
"currentCode": "example"
}'/api/v1/automations/invokeFunctionRun an account function once (manual test). Params: id (the function id OR its exact name — e.g. "qa_echo"), event? (the input it receives as params). Returns its result, runId and, when function logs are switched on, logs: its console.* lines (secrets hidden; on a failure they follow the error). WHO RUNS IT: a function runs as the workspace — whoever builds it decides what it can do and can only wire what they may use; the person who started it is passed in as data (ctx.run.startedBy: kind, and a member's id, name and role, and their email in the workspace's own functions (not an installed app's), or the agent and the person behind it, or the function that called it and who is behind that), never as rights. A site visitor's function sees only what that visitor may see. Check runner-supplied inputs (which row, which address, which amount) in the code.
Parameters
id | stringrequired | The function id — or its exact name |
event | object |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/invokeFunction \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"event": {}
}'/api/v1/automations/listTriggersList an account function's triggers (or, with kind "site", a site function's webhooks; its schedules are site_listSchedules). Params: functionId, kind? ("account" default | "site"). Returns live links (treat them as passwords; so this is a write tool that needs an edit role: a read-only connection or a Viewer is refused it) { webhooks: [{id, token, isActive, lastTriggered}], schedules: [{id, cronExpression, timezone, isActive, lastTriggered, nextTrigger}] }. Its "On event" triggers: accountEvents_listEventTriggers({ targetKind: "account_function", targetRef }); its email addresses: automations_listMailhooks (targetKind account_function).
Parameters
functionId | stringrequired | |
kind | string | Which function functionId names: 'account' (the default) for an account function, 'site' for a site's backend function (its id, from site_listBackendFunctions) |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listTriggers \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"functionId": "example",
"kind": "example"
}'/api/v1/automations/listRunsRun history of one account function, newest first: status, what started it (startedBy, in words), timing, the error line. Where switched on, page with before = the oldest run id you have
Parameters
functionId | stringrequired | |
limit | integer | |
before | string | Where switched on: the id of the oldest run you already have; answers the runs older than it (the next page) |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listRuns \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"functionId": "example",
"limit": {},
"before": "example"
}'/api/v1/automations/getRunLogThe logs of one account-function run (from listRuns): its console.* lines, secrets hidden
Parameters
runId | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/getRunLog \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"runId": "example"
}'/api/v1/automations/createWebhookAdd a webhook trigger to an account function — or, with kind "site", to a site function (its id from site_listBackendFunctions; where switched on: else refused "can't have a webhook yet") — a secret URL that runs it when POSTed (POST only: a GET answers 405). Params: functionId, kind? ("account" default | "site"). A site function's webhook runs it as the site's schedules do (its execute rule not asked; the body as params, the raw body and signing headers as params._request) and answers the caller { success, data } only. Returns { id, token }; the URL is /api/automations/functions/webhook/<token>. The function must be Active (isActive) to actually run. The URL is a password: anyone holding it starts the function, so never paste it in a page or chat; if a call also carries a site member's sign-in, the function's own execute rule is judged for that member.
Parameters
functionId | stringrequired | |
kind | string | Which function functionId names: 'account' (the default) for an account function, 'site' for a site's backend function (its id, from site_listBackendFunctions) |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/createWebhook \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"functionId": "example",
"kind": "example"
}'/api/v1/automations/setWebhookActiveEnable or disable a webhook trigger. Params: id (webhook id), isActive (boolean).
Parameters
id | stringrequired | |
isActive | booleanrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/setWebhookActive \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"isActive": true
}'/api/v1/automations/deleteWebhookDelete a webhook trigger. Param: id (webhook id).
Parameters
id | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/deleteWebhook \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example"
}'/api/v1/automations/createScheduleRun an account function on a cron (functionId), or — where switched on — one of your sites' functions (targetKind site_function + targetRef)
Parameters
functionId | string | The account function it runs (id from listFunctions). Or, instead: targetKind + targetRef |
targetKind | string | FN1g-4b, where switched on: site_function = run one of your sites' functions (targetRef = its id from site_listBackendFunctions) instead of an account function |
targetRef | string | With targetKind: the site function's id |
cronExpression | stringrequired | |
timezone | string |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/createSchedule \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"functionId": "example",
"targetKind": "example",
"targetRef": "example"
}'/api/v1/automations/setScheduleActiveEnable or disable a schedule trigger (re-registers/unregisters the CronJob). Params: id (schedule id), isActive (boolean).
Parameters
id | stringrequired | |
isActive | booleanrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/setScheduleActive \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"isActive": true
}'/api/v1/automations/deleteScheduleDelete a schedule trigger (unregisters its CronJob). Param: id (schedule id).
Parameters
id | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/deleteSchedule \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example"
}'/api/v1/automations/listMailAccountsList the account's customer-owned SMTP mailboxes (Sending accounts). Optional param "projectId" filters to mailboxes a given site may use (unbound + bound to it). Returns safe shape only — id, name, fromAddress, fromName, verified, isDefault, isActive, sentCount, hourlyLimit, site — NEVER the password. The mail-server details (host, port, secure, username) and lastError (the mail server's own words) are returned only to someone who holds the "Add and remove mailboxes" permission; for anyone else those keys are absent, which is not an error and not a missing setting — don't ask them for the server details; whoever holds that permission edits the mailbox. `site` is the ONE site the mailbox is restricted to ({ id, name }; "Restrict to one site" on Sending accounts) or null = any site, agent or automation. Check `site` before offering a mailbox for a site: a mailbox restricted to another site can NOT send for this one (the send is refused); the fix is Automations → Sending accounts → edit the mailbox → "Restrict to one site" = Any site or this site, or add a mailbox for this site. sentCount is the CURRENT HOUR'S count used against hourlyLimit, not a lifetime total: it restarts at the size of the first send once an hour has passed with no send, so a drop from 8 to 1 means a new window opened, not lost history. MULTI-TENANT: one mailbox can send under several identities — ctx.email.sendVia(name, { ..., fromName }) sets the display name PER SEND (the verified address never changes), so customers sharing a mailbox each appear as themselves. ⚠️ A send returns fromNameOverridden — that means the platform PUT the name in the From header, NOT that the recipient sees it: some SMTP hosts (shared cPanel/Exim especially) rewrite From on authenticated submission to the mailbox's own configured display name, after the message leaves us and undetectably from here. If a delivered message shows the wrong name, do NOT retry or treat it as a platform bug — set the display name on the mailbox at the mail host, or use a provider allowing per-send names (SES, Postmark, Resend, Mailgun). Every customer-written email (ctx.email.send, ctx.email.sendVia, campaigns, journeys, agents) leaves one of these mailboxes and nothing else — there is no platform sender and no platform monthly quota; only a site's sign-in emails go from the platform address. ctx.email.send uses the default mailbox this site may use; with none it is refused (403 needsMailbox) and nothing is sent.
Parameters
projectId | string | Filter to mailboxes usable by this site (unbound + bound to it) |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listMailAccounts \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"projectId": "example"
}'/api/v1/automations/createMailAccountAdd a customer-owned SMTP mailbox. Use for outreach: sends leave the customer's own domain and reputation, not the platform's shared sender, and no monthly platform quota applies.
Parameters
name | stringrequired | Reference used by ctx.email.sendVia (unique per account) |
fromAddress | stringrequired | The address recipients see |
fromName | string | |
replyTo | string | |
host | stringrequired | SMTP host, e.g. smtp.gmail.com |
port | integer | |
secure | boolean | true for implicit TLS (465); false for STARTTLS (587) |
username | stringrequired | |
password | stringrequired | SMTP password / app password — encrypted at rest, never returned |
projectId | string | Bind to ONE site; omit to let anything in the account use it |
isDefault | boolean | |
hourlyLimit | integer |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/createMailAccount \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "example",
"fromAddress": "example",
"fromName": "example"
}'/api/v1/automations/updateMailAccountUpdate a mail account. Omit password to keep the stored credential.
Parameters
id | stringrequired | |
name | string | |
fromAddress | string | The address recipients see |
fromName | string | |
replyTo | string | |
host | string | |
port | integer | |
secure | boolean | |
username | string | |
password | string | Only send when rotating the credential |
projectId | string | |
isDefault | boolean | |
isActive | boolean | |
hourlyLimit | integer |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/updateMailAccount \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"name": "example",
"fromAddress": "example"
}'/api/v1/automations/deleteMailAccountDelete a mail account by id. Functions calling ctx.email.sendVia with that name will start failing — check usage first.
Parameters
id | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/deleteMailAccount \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example"
}'/api/v1/automations/testMailAccountVerify SMTP credentials (and optionally send a test email)
Parameters
id | stringrequired | |
to | string | Send a real test email here; omit to only verify the connection |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/testMailAccount \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"to": "example"
}'/api/v1/automations/listMailhooksList the account's email triggers (Mailhooks) — each is a unique address like k3f9a2b1c7d8e9@mailhook.serenitiesai.com; any email sent or forwarded there runs its target. Optional param "projectId" also filters to hooks bound to that site. Returns the live addresses (a password-like link; so this is a write tool that needs an edit role: a read-only connection or a Viewer is refused it) { mailhooks: [{id, address, name, targetKind, targetRef, isActive, receivedCount, lastReceivedAt}] }.
Parameters
projectId | string | Filter to hooks bound to this site (plus unbound ones) |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listMailhooks \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"projectId": "example"
}'/api/v1/automations/createMailhookCreate an inbound email trigger. Returns the generated address — any email sent or forwarded to it runs the target with the parsed email as input.
Parameters
name | stringrequired | Label shown in the dashboard, e.g. "Support inbox" |
targetKind | stringrequired | What runs when an email arrives (site_function: one of your sites' functions, where switched on) |
targetRef | stringrequired | Id of the flow / specialist / account function / site function to run |
projectId | string | Optionally associate with one site (informational for flow targets) |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/createMailhook \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "example",
"targetKind": "example",
"targetRef": "example"
}'/api/v1/automations/updateMailhookRename, retarget, or pause an email trigger. targetKind and targetRef must be sent together when retargeting.
Parameters
id | stringrequired | |
name | string | |
targetKind | string | |
targetRef | string | |
isActive | boolean |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/updateMailhook \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example",
"name": "example",
"targetKind": "example"
}'/api/v1/automations/deleteMailhookPermanently delete an email trigger. Its address immediately stops accepting mail. Param: id.
Parameters
id | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/deleteMailhook \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"id": "example"
}'/api/v1/automations/listInboundEmailsRecent emails a mailhook received — the "did my trigger fire?" history
Parameters
mailhookId | stringrequired | |
limit | integer |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listInboundEmails \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"mailhookId": "example",
"limit": {}
}'/api/v1/automations/listEnvVarsList GLOBAL (account-level) secrets (env vars; Automations → Secrets tab) — name, description, updatedAt. Values are NEVER returned. These are what ACCOUNT functions (ctx.secrets.ref), agents (their run's markers) and flows ({{secret:NAME}} in a step's settings) use, from their lists; per-site env vars are a separate store (site listEnvVars). allowedHosts: where each may ever be sent.
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listEnvVars \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{}'/api/v1/automations/setEnvVarCreate or update a GLOBAL (account-level) env var. Value is encrypted at rest and never returned. Creating an EXISTING name fails unless overwrite:true — pass it only when deliberately rotating a value. allowedHosts (optional): the hosts it may ever be sent to — a function's, agent's or flow's list can never send it further.
Parameters
name | stringrequired | |
value | stringrequired | |
description | string | |
overwrite | boolean | |
allowedHosts | array |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/setEnvVar \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "example",
"value": "example",
"description": "example"
}'/api/v1/automations/deleteEnvVarDelete a GLOBAL (account-level) env var by name. Account functions reading it will start failing — check usage first. For a secret that a function with full data access reads, this tool is refused: ask the Owner or an Admin to delete it in the dashboard.
Parameters
name | stringrequired |
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/deleteEnvVar \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "example"
}'/api/v1/automations/listAllWebhooksList every webhook trigger of this workspace in one list, across its kinds, newest first. Takes no params. Each item: id, lane ("flow": starts a flow; "specialist": gives an AI agent a task; "account_function": runs an account function; "site_function": runs a site function, with siteId and siteName), targetId and targetName (that flow, agent or function), triggerId (a flow's trigger step; null otherwise), isActive, lastTriggered, createdAt. The secret link itself is never returned here: whoever has it can start the automation, so the person copies it from the Triggers page. Pause or resume one with automations_setWebhookActive (a function's) or aiAgent_setWebhookActive (an agent's), only when the person asks. Needs the right to edit functions; someone who may only look is refused.
Example Request
curl -X POST https://app.serenitiesai.com/api/v1/automations/listAllWebhooks \
-H "Authorization: Bearer mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{}'