# Take over a mail trigger

POST

/v1/mail-triggers/{triggerId}/owner

```sh
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

- **[bearerAuth](https://docs.kindgi.com/v0.5.0/reference/api.md#bearerauth)**

## Parameters

### Path Parameters

**triggerId**

required

string format: uuid

TriggerId — the trigger id (a UUID).

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

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

application/json

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

**runAsServiceAccountId**

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`).

string

\>= 1 characters

**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.

boolean

##### Examplegenerated

```json
{
  "runAsServiceAccountId": "example",
  "runAsProject": true
}
```

## Responses

### 200

The trigger, with its new owner.

application/json

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

**triggerId**

required

string

**flowId**

Run a flow (with `flowVersion`).

string

\>= 1 characters

**flowVersion**

string

\>= 1 characters

**agentId**

Run an agent (instead of a flow): at `agentVersion`, else its pinned version.

string

\>= 1 characters

**agentVersion**

string

\>= 1 characters

**projectId**

required

The trigger’s project: its runs are this project’s.

string format: uuid

**owner**

required

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

**kind**

required

string

Allowed values: user service

**id**

required

string

**displayName**

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.

string

**isProjectAccount**

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.

boolean

**mailbox**

required

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

**app**

required

string

Allowed values: google microsoft

**as**

required

string

Allowed values: company person

**connectionId**

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.

string format: uuid

**folder**

The Gmail label id, or the Outlook folder id or well-known name, it watches. Absent: the inbox.

string

\>= 1 characters <= 512 characters

**pollSeconds**

required

How often it looks for new mail, in seconds (default 60).

integer

\>= 30 <= 3600

**runsPerHour**

required

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.

integer

\>= 1 <= 600

**label**

required

string | null

**status**

required

Lifecycle status. Only `active` triggers fire. Tombstoned rows are excluded from every read path.

string

Allowed values: active paused

**statusReason**

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.

string

**version**

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.

integer

\>= 1

**account**

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

**id**

required

string

**label**

required

string

**lastPolledAt**

When it last polled the mailbox and got an answer.

string format: date-time

**toolsAllowedUnattended**

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.

Array\<string>

<= 100 items unique items

**lastFiredAt**

required

string | null format: date-time

**createdAt**

required

string format: date-time

**updatedAt**

required

string format: date-time

##### Example

```json
{
  "owner": {
    "kind": "user",
    "isProjectAccount": true
  },
  "mailbox": {
    "app": "google",
    "as": "company"
  },
  "status": "active"
}
```

### 400

`bad-input`: a body naming `toolsAllowedUnattended`, a service account or `runAsProject` for a person’s mailbox, or `runAsProject` with `runAsServiceAccountId`.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 404

No mail trigger with that id, or no such service account.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

`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.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

The request body is over the API’s limit (`request-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 501

`trigger-operation-unsupported`: this deployment can’t change a trigger’s owner, or can’t make a project’s own account (`runAsProject`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 503

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.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/mail-triggers/{triggerId}`: Fetch a mail trigger](https://docs.kindgi.com/v0.5.0/reference/api/operations/mailtriggersget.md)
- [`PATCH /v1/mail-triggers/{triggerId}`: Update a mail trigger](https://docs.kindgi.com/v0.5.0/reference/api/operations/mailtriggersupdate.md)
- [`POST /v1/mail-triggers/{triggerId}/pause`: Pause a mail trigger](https://docs.kindgi.com/v0.5.0/reference/api/operations/mailtriggerspause.md)
- [`POST /v1/mail-triggers/{triggerId}/resume`: Resume a mail trigger](https://docs.kindgi.com/v0.5.0/reference/api/operations/mailtriggersresume.md)
- [Error code `bad-input`](https://docs.kindgi.com/v0.5.0/reference/errors.md#bad-input)
- [Error code `idempotency-store-unavailable`](https://docs.kindgi.com/v0.5.0/reference/errors.md#idempotency-store-unavailable)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.5.0/reference/errors.md#request-too-large)
- [Error code `service-account-name-taken`](https://docs.kindgi.com/v0.5.0/reference/errors.md#service-account-name-taken)
- [Error code `service-account-project-owned`](https://docs.kindgi.com/v0.5.0/reference/errors.md#service-account-project-owned)
- [Error code `service-account-unregistered`](https://docs.kindgi.com/v0.5.0/reference/errors.md#service-account-unregistered)
- [Error code `trigger-operation-unsupported`](https://docs.kindgi.com/v0.5.0/reference/errors.md#trigger-operation-unsupported)
- [Error code `trigger-owner-cannot-run-target`](https://docs.kindgi.com/v0.5.0/reference/errors.md#trigger-owner-cannot-run-target)
- [Every error code](https://docs.kindgi.com/v0.5.0/reference/errors.md)
