Transfer template ownership
/template/{id}/transfer
Changes `owner_user_id` — and nothing else. Every existing grant is left exactly as it was. Allowed for the current owner only — no `template:manage` fallback.
Path Parameters
Template id (tpl_…).
Request Body
New owning member (usr_…).
Response Body
The caller's access: owner, or a granted view/use/edit level. Templates carry no organization-wide oversight path — an org owner/admin sees a template only when they own it or it has been shared with them, same as any other member.
Present on a GET /template row exactly when that row's can_see_shares is true — the caller owns it, holds edit, or administers a team it is granted to. Same gate and same reasoning as the toolbox row's.
Level of the org-wide grant, or null when there is none.
viewuseeditnull
At most 5 grantees, broadest first (org, then teams, then members), for a hover preview.
4 properties
userteamorg
viewuseedit
Display name; null for org grants and for grantees no longer in the org.
Every grant, the org-wide one included.
How the CALLER reaches this template — not who else can. owner — they own it. direct — a grant naming them personally. team — a grant to a team they belong to (or, per §6.2, one they administer), named in access_via_team. org — an organization-wide grant. When several sources apply the BROADEST wins and the caller's access level is not consulted: owner, else org, else team, else direct. So a caller granted edit personally AND view org-wide reads org — a narrower grant must never mask org-wide exposure, since this field says how far the template reaches, not what the caller may do with it. Deliberately NOT gated on can_see_shares, and deliberately not a widening of it: this is the caller's OWN grant and their OWN team memberships, so a view/use grantee receives it while the grantee list — information about colleagues — stays closed to them. No other member is ever named. ABSENT when nothing reaches the caller — a transfer answering the ex-owner of a resource that was never shared, or a catalog row browsed with no grant behind it. Absent means "no source to name", never "not permitted", and never an implied org.
ownerdirectteamorg
Present exactly when access_via is team, absent otherwise. The team the caller reaches this template through — one of their own teams, never a disclosure about anybody else.
Team id (team_…).
Team display name, or null when the team no longer resolves in the directory — the same null contract every resolved grantee name carries.
Whether the caller may edit this template's own settings — owner, or edit access. No template:manage fallback. Mirrors PATCH /template/{id}.
can_share, verbatim — a THIRD formula, distinct from can_manage: an edit grantee whose role omits template:share can edit the template but was never meant to grant or revoke someone else's access to it. Mirrors DELETE /template/{id}/share/{aclId}.
Whether the caller may share this template — owner, edit access, or template:share. No template:manage fallback. Mirrors POST /template/{id}/share's own check.
Whether the caller may transfer or delete this template — ownership, full stop. No template:manage fallback. Mirrors POST /template/{id}/transfer and DELETE /template/{id}.
Whether the caller may stamp a toolbox from this template — the caller's grant-or-ownership level (§10). Present on every list and detail row for both templates and toolboxes — the two ACL-backed resource types with a use-gated action of their own.
Which integrations this template's tools come from, bounded — the console renders it as a stack of connector logos, exactly as on a toolbox row. Present on every GET /template row AND on every command response (POST /template, GET /template/{id}, PATCH /template/{id}, POST /template/{id}/transfer), so a client that merges a command response into its list does not lose the stack. total: 0 means no connector-backed tools (empty, or synthetic-only), never "not computed".
At most 5 connectors, ordered by entry count descending then slug ascending — the dominant integration leads and the order is stable across requests. Each entry arrives resolved: there is no follow-up GET /connector/{slug} to make, and a page of rows costs no per-row catalog lookup.
3 properties
The connector's square icon when it has one, else its wordmark logo, else null (the connector carries neither picture, or could not be resolved). Draw it in a square tile; render initials from name when it is null.
Catalog label. Falls back to the slug when the connector no longer resolves (deleted from the catalog), so a row always has something to draw.
Connector slug — connectors are keyed by slug, not by id.
Distinct connector slugs across the template.
Template id (tpl_…).
Owner summary — resolved for a row the caller does not themselves own (a toolbox shared with them), so the UI always knows whose row it is looking at. Absent for a toolbox the caller owns.
User id (usr_…) — same value as owner_user_id.
Creator (usr_…).
Every grant, unabridged. Present only on GET /template/{id} when the caller may see the ACL (owner or edit access); list rows carry the bounded access_summary instead.
User id (usr_…) or team id (team_…). Null for a grantee_type: "org" grant.
user and team grants target one grantee_id; org applies to every member of the organization and takes no id.
userteamorg
ACL entry id — the :aclId a DELETE .../share/{aclId} call takes.
Same ladder for every shareable resource, low to high. view — see it exists, read its metadata/config; cannot exercise it. use — view + exercise it, resource-specific: run tools through a connection; execute a toolbox's tools through its bound connections (this DELEGATES — the caller runs through each entry's pinning editor's own authority, not necessarily their own); stamp a new toolbox by copying a template's entries; create connections from a connector. edit — use + change its settings, entries and its own grants.
viewuseedit
The shared resource's id (a connector's slug, for that type).
connectiontoolboxtemplateconnector
curl -X POST 'https://api.elaichi.ai/template/<id>/transfer' \
-H 'Authorization: Bearer $ELAICHI_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"owner_user_id":"your_owner_user_id"}'const body = {
"owner_user_id": "your_owner_user_id"
};
const response = await fetch('https://api.elaichi.ai/template/<id>/transfer', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.ELAICHI_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await response.json();
console.log(data);import os
import requests
url = "https://api.elaichi.ai/template/<id>/transfer"
headers = {
"Authorization": f"Bearer {os.environ['ELAICHI_API_TOKEN']}",
"Content-Type": "application/json",
}
payload = {
"owner_user_id": "your_owner_user_id"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())