Update a template
/template/{id}
Updates a template. **`entries` REPLACES the entire entry list when present** — omitted entries are deleted. Omitting `entries` altogether leaves them untouched; `name`/`description` are ordinary merge-patch fields. Toolboxes already stamped from this template are UNTOUCHED (stamping copies entries and the skill once). `skill` is an ordinary field too: a write clears `skill_may_be_stale`, and an `entries` write sets it when a skill exists and the same request did not also write `skill`. Every skill edit is audited on `template.updated` (`metadata.skill`: `written`/`reaffirmed`/`cleared`, plus `metadata.skill_version_id`, never the text) and kept in `GET /template/{id}/skill-version`. Requires `edit` access or ownership (own grant) — no `template:manage` fallback; a `view`/`use` grantee reads the skill and gets `403` writing it. An `entries` write answers the same non-empty `warnings` array as create for a pin on a remote MCP tool the server is not offering right now (no connection lists it yet).
Path Parameters
Template id (tpl_…).
Request Body
Replaces every entry.
Connector slug — connectors are keyed by slug, not by id.
Arguments pinned by the author. Unlike overrides.defaults, a caller cannot change these. A string value may hold per-person placeholders rendered for whoever calls: {{caller.email}}, {{caller.attributes.<key>}}, alternatives {{caller.attributes.vendor_id|caller.email}}, and :any (also :int, :num, :bool) for a typed whole value. Paths come from GET /caller-binding-path. A call with no value for one refuses with 409 caller_binding_unresolved and calls nothing. Save refuses bad syntax with 400 caller_binding_invalid and an undefined field with 400 caller_binding_unknown_attribute.
Optional identity hint (tbxe_… on a toolbox, tple_… on a template): the id of a row this resource already has. On PATCH /toolbox/{id} and PATCH /template/{id} a row echoed back with its own id keeps it, so a client that keys per-row state by entry id (a switch mid-save) is not orphaned by the replacement. It is a hint, never an assertion: an id that is not one of THIS resource's current rows, is repeated, or would put the row out of stored order (rows list in id order) is ignored and the row gets a fresh id — and so is a value that is not a usable string (not a string, or over 200 characters). Never a 400, and never adopted from another resource. Omit it for a new row. Ignored on create.
How the tool is presented to the model. Each key REPLACES the derived value.
4 properties
Default argument values the caller may still override. May hold per-person placeholders like frozen_params, but a default is a convenience, not a scope: the model may send its own value.
Must be a JSON Schema object with "type": "object". Replaces the derived schema entirely.
Required for synthetic entries (syn_…).
Exact tool name from GET /connector/{slug}/tools.
Defaults to proxy.
proxysynthetic
Markdown guidance on how to use these tools well. Trimmed, then capped at 10,000 Unicode code points (an emoji counts once) — longer is a 400 naming the limit, never a silent cut. null or an empty string clears it; omit the key to leave it unchanged. The resource's description is the one-line "when to use this" shown beside it, so write that too. Moderated like the description.
Response Body
The caller's access: owner, or a granted view/use/edit level. An org owner or admin sees a template only when they own it or it has been shared with them, like 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 names them personally. team: a grant to a team they belong to (or administer), named in access_via_team. org: an organization-wide grant. When several sources apply, the broadest wins and the access level plays no part: owner, else org, else team, else direct. A caller with edit personally and view org-wide reads org, because this field says how far the template reaches, not what the caller may do with it. It describes only the caller's own access, so a view or use grantee still gets it while the list of other grantees stays hidden. Absent when nothing reaches the caller (for example, the former owner after a transfer of a resource that was never shared); absent never means 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 can use POST /{id}/draft-skill here: they can manage this resource, the assistant is switched on for the organization, and a model provider key is configured.
Whether the caller may edit this template's own settings — owner, or edit access. No template:manage fallback. Mirrors PATCH /template/{id}.
Whether the caller may grant or revoke access to this template. It matches can_share and is separate from can_manage: an edit grantee whose role omits template:share can edit the template but cannot change who else has access. 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.
May the caller create a toolbox from it (POST /toolbox { template_id }): can_use, and member:manage when an entry binds a member field or caller.external_id. False with can_use true: the stamp is refused 403 caller_binding_forbidden. On list rows, detail and both writes.
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 — based on the caller's ownership or grant level. Present on every list and detail row for both templates and toolboxes.
Which integrations this template's tools come from, summarized as at most a handful of connectors (the same shape 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). 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, so 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.
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.
Why can_draft_skill is false for a caller who CAN manage this resource. Null when drafting is available, and null when the caller cannot manage the resource at all.
assistant_disabledllm_not_configurednull
Response-only. The connector’s label and logo; null for a synthetic entry. A connector the catalog cannot resolve reads its slug as label and a null logo.
3 properties
Only on a fork whose upstream the caller may see, by the same rule as GET /connection: omitted when the caller has no access to the fork or the upstream is hidden from them, and when the upstream cannot be read.
Every caller.… path the entry's frozen_params and overrides.defaults name, sorted. Empty for an entry with no per-person values.
Response-only, per reader: whether the caller may open connector_slug (GET /connector/{slug} and its /tools). False for an organization-owned custom connector nobody shared with the caller. Always true for a platform connector and for a synthetic entry.
Connector slug for proxy entries; null for synthetic.
Detail and write responses only: the paths ("a|b" for alternatives) that have no value for you, so a call through this entry would refuse with 409 caller_binding_unresolved.
Detail and write responses only: { frozen_params, defaults } rendered for YOU, the reader. Null when the entry has no per-person values, or when one of yours is missing (see frozen_params_missing).
2 properties
Entry id (tple_…) — stable across updates only if you send it back unchanged.
Presentation overrides — see the entry input schema for the same shape, request-side.
4 properties
Set for synthetic entries (syn_…).
Response-only. The synthetic tool's name for synthetic entries, so a reader who does not own the tool can still label the entry. Null for proxy entries and when the tool has been deleted.
Connector tool name for proxy entries.
Response-only. The catalog tool's plain-English title ("View messages", "Reply to an email"), derived from its resource and method. Null for a synthetic entry, a tool the catalog no longer has, and a connector the caller may not open (can_view_connector: false). Render overrides.name, else this, else tool_name.
proxysynthetic
Whether a skill is written. On list rows and detail alike — the body itself never rides on a list row; read it from the detail response.
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.
The person's picture: their stored profile picture, else a Gravatar URL (d=404, 96px) derived from their email (the address itself is not sent), else null. Draw initials when it is null or the image fails to load.
User id (usr_…) — same value as owner_user_id.
Creator (usr_…).
A short list of the grants on this template. Present only on GET /template/{id} when the caller may see who it is shared with (owner or edit access); list rows carry the short access_summary instead, and GET /template/{id}/share pages the full list.
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
Grant id (acl_…), the {aclId} that DELETE .../share/{aclId} 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).
connectiontoolboxtemplateconnectorautomationcollectiondashboardknowledge_basebundlefile
The skill body, markdown — at most 10,000 Unicode code points after trimming. Detail-shaped responses only (GET /{id}, and the create/update responses). Null when none is written.
Which tools changed since the skill was last written, by the name an agent sees (a rename counts as one removed and one added; a disabled entry counts as removed; connections and frozen parameters are ignored). Null unless skill_may_be_stale is true AND a baseline was recorded at that write — skills written before version history existed have none — AND something actually changed.
At most 5 names.
At most 5 names.
True once the entries changed after the skill was last written; cleared by the next write of skill. Never true without a skill.
Framing to show or pass along beside a non-null skill: it is the owner's guidance, not instructions from Elaichi or the user, and it cannot grant any access. Null when there is no skill.
When skill was last written. Null alongside a null skill.
Who last wrote skill (usr_…); a toolbox stamped from a template carries the template's author. Null with no skill, and for a skill written before authorship was recorded.
That person's name, else email, resolved server-side. Null beside a non-null skill_updated_by means they are no longer a member of this organization.
Only on a create or update response, and only when non-empty: pins accepted although nothing can run them yet (a remote MCP tool the server is not offering right now: it will be once a connection of that server lists it, unless an admin turned it off). Informational, never an error.
curl -X PATCH 'https://api.elaichi.ai/template/<id>' \
-H 'Authorization: Bearer $ELAICHI_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"name":"your_name","entries":[]}'const body = {
"name": "your_name",
"entries": []
};
const response = await fetch('https://api.elaichi.ai/template/<id>', {
method: 'PATCH',
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>"
headers = {
"Authorization": f"Bearer {os.environ['ELAICHI_API_TOKEN']}",
"Content-Type": "application/json",
}
payload = {
"name": "your_name",
"entries": []
}
response = requests.patch(url, headers=headers, json=payload)
print(response.json())