Take over a mail trigger
const url = 'https://example.com/v1/mail-triggers/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/owner';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"runAsServiceAccountId":"example","runAsProject":true}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/v1/mail-triggers/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/owner \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "runAsServiceAccountId": "example", "runAsProject": true }'The only way a mail trigger’s owner changes. The caller becomes it, so its runs act as the caller, with the caller’s connections, from the next poll; on a person’s mailbox, the mailbox becomes the caller’s own, and the next poll starts afresh. Needs admin on the trigger’s project and execute on its target. On the company’s mailbox, name runAsServiceAccountId instead to make a service account the owner: that needs act_as on the account, and the account must itself hold what its target needs. With an authorization store, that’s checked when the account is named: an account that can’t is refused (409 trigger-owner-cannot-run-target). Or name runAsProject: true to make the project’s own service account the owner, made the first time with the editor role on the project. It runs only what the project’s editors can, and is checked the same way. The trigger keeps its toolsAllowedUnattended; a body naming it is refused (change it with PATCH).
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”TriggerId — the trigger id (a UUID).
Header Parameters
Section titled “Header Parameters”Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the workspace is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per docs/API-ROUTE-CONVENTIONS.md §3.1). A retry sent while the first request still runs is answered 409 idempotency-key-in-flight with Retry-After, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets 409 idempotency-key-replay-withheld, with the first request’s status and when it succeeded (from 0.1.5). A request whose key store can’t be read before it runs is answered 503 idempotency-store-unavailable with Retry-After: nothing ran, so retry with the same key (from 0.3.0).
Request Body
Section titled “Request Body”Who the trigger’s runs act as from now: for a trigger whose owner left, or to make one shared or personal. Absent, or an empty body: the caller takes it over (today’s behaviour). With runAsServiceAccountId: that service account becomes the owner. With runAsProject: the project’s own account does. A take-over keeps the trigger’s toolsAllowedUnattended: change that with PATCH.
object
A service account the trigger’s runs act as (its owner), named by id, instead of the caller. Needs act_as on that account — a tenant admin holds it — and the account must itself hold what the trigger runs. With an authorization store, that’s checked when the account is named: an account that can’t is refused (409 trigger-owner-cannot-run-target). Not a project’s own account (409 service-account-project-owned: name it with runAsProject).
true: the trigger’s runs act as its project’s own service account, made the first time with the editor role on the project, so it’s shared. It runs only what the project’s editors can. With an authorization store, that’s checked when it’s named: if it can’t run what the trigger runs, the request is refused (409 trigger-owner-cannot-run-target). Takes admin on the trigger’s project (without an authorization store, a tenant-admin key), not act_as. Not with runAsServiceAccountId, and not without a project (400). A runtime that can’t make the account answers 501 trigger-operation-unsupported. false or absent: as runAsServiceAccountId says.
Examplegenerated
{ "runAsServiceAccountId": "example", "runAsProject": true}Responses
Section titled “Responses”The trigger, with its new owner.
A mail trigger: each new mail in its mailbox’s folder starts a run of its agent or flow, with the mail as the run’s input, as its owner. In such a run, every call that changes something, or uses the run’s own mailbox, waits for a person’s yes unless the trigger lists it (toolsAllowedUnattended).
object
Run a flow (with flowVersion).
Run an agent (instead of a flow): at agentVersion, else its pinned version.
The trigger’s project: its runs are this project’s.
Who its runs act as, with their connections. A service account owns a shared trigger, on the company’s mailbox: one named with runAsServiceAccountId, or the project’s own (runAsProject, isProjectAccount). A person owns a personal one: every trigger on a person’s mailbox, and one on the company’s mailbox registered without either. No edit moves it; only a take-over (POST …/owner) does.
object
The owner’s name at the time of the response: the person’s display name, or the service account’s name. Absent when it can’t be read (no directory, a removed account) and from a runtime before Kindgi 0.1.6: show the id then.
Set when the owner is the trigger’s project’s own service account (runAsProject): the trigger is shared, and runs as the project. Show it as “This project”. Absent otherwise.
The mailbox a mail trigger reads: its app (google: Gmail; microsoft: Outlook) and whose it is (company: the company’s mailbox, through the company connection of that app connectionId names; person: the trigger owner’s own, through their connection).
object
The connection it reads the mailbox with. With as: company, it must be one of the project’s candidates of the app (GET /v1/connection-choices/candidates); a person’s own live connection of the app is 400 bad-input, and any other 404 connection-not-found. Not named, the project’s only one is stored, so another connection added later doesn’t change what the trigger reads; with several, it’s 409 connection-not-chosen, and with none, nothing is stored. With as: person, it must be the owner’s own live connection of the app, and any other is 404 connection-not-found. Not named, their main is stored, else their only one; with several and no main, it’s 409 connection-not-chosen, which counts them; with none, nothing is stored. A new owner clears it, and the next poll stores theirs. Another connectionId, or one named where there was none, is another mailbox. The mailbox it reads now, sent again with none, is the same mailbox: it keeps the one it reads with, or stores the one the rules above give. Absent from a trigger that names none.
The Gmail label id, or the Outlook folder id or well-known name, it watches. Absent: the inbox.
How often it looks for new mail, in seconds (default 60).
The most runs its mail starts in an hour (default 60). Past it, a mail’s fire is refused (detail starts over-limit:), and that mail doesn’t run later.
Lifecycle status. Only active triggers fire. Tombstoned rows are excluded from every read path.
Why it isn’t polling as it should: connection-missing (the mailbox’s connection is gone or lapsed; polls go on, and work again once it’s back), connection-not-chosen (it names no connection, and the project has several company connections of the app, which the reason names, or its owner has several and no main; it doesn’t poll until its config.mailbox.connectionId names one), owner-refused (its owner was removed or may no longer run the target; polls go on and read nothing until that changes or an admin takes it over), or why the runtime paused it.
The trigger’s newest version (GET …/versions): one more for each change to what it runs, when, as whom or with what. Absent until its first version.
The mailbox’s account at its last poll: the connection’s account id and its label (the address). Absent before its first poll, and after a take-over of a person’s mailbox until the next poll. On a person’s mailbox (config.mailbox.as: person), only the trigger’s owner and a tenant admin read it; anyone else reads the trigger without it.
object
When it last polled the mailbox and got an answer.
The tools whose calls in this trigger’s runs don’t wait for a person’s yes, though a run a mail started asks for one for every call that changes something or uses its mailbox. Absent: none. Each tool by its id, with no wildcard or pattern, and only tools this trigger’s flow or agent calls. It lifts only that wait: the workspace’s approval rules, the agent’s own rules, the permission check (as the trigger’s owner) and the tool’s own limits all still apply.
Example
{ "owner": { "kind": "user", "isProjectAccount": true }, "mailbox": { "app": "google", "as": "company" }, "status": "active"}bad-input: a body naming toolsAllowedUnattended, a service account or runAsProject for a person’s mailbox, or runAsProject with runAsServiceAccountId.
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}Missing / malformed / expired / revoked bearer token.
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}No mail trigger with that id, or no such service account.
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}runAsServiceAccountId names an unregistered account (service-account-unregistered), a project’s own account (service-account-project-owned: name it with runAsProject), or one that can’t run what the trigger runs (trigger-owner-cannot-run-target). Or, with runAsProject, the project’s own account can’t run what the trigger runs either (trigger-owner-cannot-run-target; the account stays, for the next runAsProject), or an active account someone made already has the project account’s name, project-<projectId> (service-account-name-taken: a tenant admin can unregister it). Or the Idempotency-Key was reused with a different body, or a resource-state conflict.
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}The request body is over the API’s limit (request-too-large; details.maxBytes says how much).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}Server error (unmapped domain code or framework crash).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}trigger-operation-unsupported: this deployment can’t change a trigger’s owner, or can’t make a project’s own account (runAsProject).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}The Idempotency-Key store couldn’t be read before the request ran (idempotency-store-unavailable), so nothing was changed; Retry-After (whole seconds) says when to try again with the same key.
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}See also
GET /v1/mail-triggers/{triggerId}: Fetch a mail triggerPATCH /v1/mail-triggers/{triggerId}: Update a mail triggerPOST /v1/mail-triggers/{triggerId}/pause: Pause a mail triggerPOST /v1/mail-triggers/{triggerId}/resume: Resume a mail trigger- Error code
bad-input - Error code
idempotency-store-unavailable - Error code
request-too-large - Error code
service-account-name-taken - Error code
service-account-project-owned - Error code
service-account-unregistered - Error code
trigger-operation-unsupported - Error code
trigger-owner-cannot-run-target - Every error code