# Update a template

> Source: https://elaichi.ai/docs/api-reference/templates/template/updatetemplate/

`PATCH /template/{id}`

Resource: **Template** · API: **Templates**

## Path parameters

- **`id`** _(string, required)_
  Template id (`tpl_…`).

## Request body

- **`name`** _(string)_
- **`description`** _(string,null)_
- **`entries`** _(array<object>)_
  Replaces every entry.
  - **`type`** _(string)_
    Defaults to `proxy`.
    Allowed: `proxy`, `synthetic`
  - **`synthetic_tool_id`** _(string,null)_
    Required for `synthetic` entries (`syn_…`).
  - **`connector_slug`** _(string,null)_
    Connector slug — connectors are keyed by slug, not by id.
  - **`tool_name`** _(string,null)_
    Exact tool name from `GET /connector/{slug}/tools`.
  - **`overrides`** _(object)_
    How the tool is presented to the model. Each key REPLACES the derived value.
    - **`name`** _(string)_
    - **`description`** _(string)_
    - **`input_schema`** _(object)_
      Must be a JSON Schema object with `"type": "object"`. Replaces the derived schema entirely.
    - **`defaults`** _(object)_
      Default argument values the caller may still override.
  - **`frozen_params`** _(object)_
    Arguments pinned by the author. Unlike `overrides.defaults`, a caller cannot change these.
  - **`enabled`** _(boolean)_
  - **`id`** _(string,null)_
    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.
- **`skill`** _(string,null)_
  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

- **`id`** _(string)_
  Template id (`tpl_…`).
- **`name`** _(string)_
- **`description`** _(string,null)_
- **`owner_user_id`** _(string)_
  Creator (`usr_…`).
- **`access_level`** _(string)_
  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.
- **`owner`** _(object,null)_
  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.
- **`entry_count`** _(integer)_
- **`connectors`** _(object)_
  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".
  - **`total`** _(integer)_
    Distinct connector slugs across the template.
  - **`preview`** _(array<object>)_
    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.
    - **`slug`** _(string)_
      Connector slug — connectors are keyed by slug, not by id.
    - **`name`** _(string)_
      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.
    - **`logo`** _(string,null)_
      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.
- **`created_at`** _(string)_
- **`updated_at`** _(string)_
- **`access_summary`** _(object)_
  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.
  - **`org_level`** _(string,null)_
    Level of the org-wide grant, or null when there is none.
    Allowed: `view`, `use`, `edit`, `null`
  - **`team_count`** _(integer)_
  - **`user_count`** _(integer)_
  - **`total`** _(integer)_
    Every grant, the org-wide one included.
  - **`preview`** _(array<object>)_
    At most 5 grantees, broadest first (org, then teams, then members), for a hover preview.
    - **`grantee_type`** _(string)_
      Allowed: `user`, `team`, `org`
    - **`grantee_id`** _(string,null)_
    - **`level`** _(string)_
      Allowed: `view`, `use`, `edit`
    - **`name`** _(string,null)_
      Display name; null for org grants and for grantees no longer in the org.
- **`shares`** _(array<object>)_
  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.
  - **`id`** _(string)_
    ACL entry id — the `:aclId` a `DELETE .../share/{aclId}` call takes.
  - **`resource_type`** _(string)_
    Allowed: `connection`, `toolbox`, `template`, `connector`, `file`
  - **`resource_id`** _(string)_
    The shared resource's id (a connector's slug, for that type).
  - **`grantee_type`** _(string)_
    `user` and `team` grants target one `grantee_id`; `org` applies to every member of the organization and takes no id.
    Allowed: `user`, `team`, `org`
  - **`grantee_id`** _(string,null)_
    User id (`usr_…`) or team id (`team_…`). Null for a `grantee_type: "org"` grant.
  - **`level`** _(string)_
    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.
    Allowed: `view`, `use`, `edit`
  - **`created_at`** _(string)_
  - **`updated_at`** _(string)_
- **`has_skill`** _(boolean)_
  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.
- **`skill_may_be_stale`** _(boolean)_
  True once the entries changed after the skill was last written; cleared by the next write of `skill`. Never true without a skill.
- **`can_use`** _(boolean)_
  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.
- **`can_share`** _(boolean)_
  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.
- **`can_manage`** _(boolean)_
  Whether the caller may edit this template's own settings — owner, or `edit` access. No `template:manage` fallback. Mirrors `PATCH /template/{id}`.
- **`can_transfer`** _(boolean)_
  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}`.
- **`can_revoke_share`** _(boolean)_
  `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}`.
- **`access_via`** _(string)_
  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`.
  Allowed: `owner`, `direct`, `team`, `org`
- **`access_via_team`** _(object)_
  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.
  - **`id`** _(string)_
    Team id (`team_…`).
  - **`name`** _(string,null)_
    Team display name, or null when the team no longer resolves in the directory — the same null contract every resolved grantee name carries.
- **`entries`** _(array<object>)_
  - **`id`** _(string)_
    Entry id (`tple_…`) — stable across updates only if you send it back unchanged.
  - **`type`** _(string)_
    Allowed: `proxy`, `synthetic`
  - **`synthetic_tool_id`** _(string,null)_
    Set for `synthetic` entries (`syn_…`).
  - **`synthetic_tool_name`** _(string,null)_
    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_slug`** _(string,null)_
    Connector slug for `proxy` entries; null for synthetic.
  - **`tool_name`** _(string,null)_
    Connector tool name for `proxy` entries.
  - **`tool_title`** _(string,null)_
    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`.
  - **`connector`** _(object,null, required)_
    Response-only. The connector's label and logo, one batch per response; null for a `synthetic` entry. A connector the catalog cannot resolve reads its slug as `label` and a null `logo`.
  - **`can_view_connector`** _(boolean)_
    Response-only, per READER: may the caller open `connector_slug` (`GET /connector/{slug}` and its `/tools`)? False for an organization-owned custom connector nobody shared with the caller (docs/access-model.md §4). Always true for a platform connector and for a synthetic entry.
  - **`overrides`** _(object)_
    Presentation overrides — see the entry input schema for the same shape, request-side.
    - **`name`** _(string)_
    - **`description`** _(string)_
    - **`input_schema`** _(object)_
    - **`defaults`** _(object)_
  - **`frozen_params`** _(object)_
  - **`enabled`** _(boolean)_
- **`skill`** _(string,null)_
  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.
- **`skill_updated_at`** _(string,null)_
  When `skill` was last written. Null alongside a null `skill`.
- **`skill_notice`** _(string,null)_
  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.
- **`skill_updated_by`** _(string,null)_
  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.
- **`skill_updated_by_label`** _(string,null)_
  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.
- **`skill_entry_changes`** _(object,null)_
  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.
- **`can_draft_skill`** _(boolean)_
  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.
- **`draft_skill_blocked_by`** _(string,null)_
  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.
  Allowed: `assistant_disabled`, `llm_not_configured`, `null`
- **`warnings`** _(array<object>)_
  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.
  - **`connector_slug`** _(string)_
  - **`tool_name`** _(string)_
  - **`message`** _(string)_

## Code examples

### curl

```bash
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":[]}'
```

### JavaScript

```javascript
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);
```

### Python

```python
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())
```
