---
title: "Create a template"
method: POST
path: "/template"
tags: ["Templates"]
---

# Create a template

`POST /template`

## Request

- Base URL: `https://api.elaichi.ai`
- URL: `https://api.elaichi.ai/template`
- Auth: HTTP bearer

Creates a template, a reusable set of tool entries. Requires `template:create`. Entries are validated against the connector catalog and the caller's restrictions as a set — one bad name rejects the whole call. `shares` grants access at creation time and additionally needs `template:share`. A template never references a connection, so there is no connection warning; the one thing it can warn about is a pin on a remote MCP tool the server is not offering right now (no connection lists it yet), which is accepted and answered with a non-empty `warnings` array (`{ connector_slug, tool_name, message }`) on the 201. `warnings` is absent when there is nothing to say.

## Headers

- `X-Organization-Id` string

## Request body

- object
  - `name` string, required
  - `description` string, nullable
  - `entries` object[]
    - `type` 'proxy' | 'synthetic': Defaults to `proxy`.
    - `synthetic_tool_id` string, nullable: Required for `synthetic` entries (`syn_…`).
    - `connector_slug` string, nullable: Connector slug — connectors are keyed by slug, not by id.
    - `tool_name` string, nullable: 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, nullable: 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.
  - `shares` ShareRequest[]: Grants to create alongside the resource. Omit, or send `[]`, for a private one. A non-empty array additionally needs `<resource>:share`.
    - `grantee_type` 'user' | 'team' | 'org', required: `user` and `team` grants target one `grantee_id`; `org` applies to every member of the organization and takes no id.
    - `grantee_id` string, nullable: User id (`usr_…`) or team id (`team_…`). Omit or send null for `grantee_type: "org"`.
    - `level` 'view' | 'use' | 'edit', required: 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.
  - `skill` string, nullable: 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. Copied onto each toolbox later stamped from this template.

## Response `201`

Created

- object
  - `id` string: Template id (`tpl_…`).
  - `name` string
  - `description` string, nullable
  - `owner_user_id` string: Creator (`usr_…`).
  - `access_level` string: 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.
  - `owner` object, nullable: 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.
    - `id` string: User id (`usr_…`) — same value as `owner_user_id`.
    - `name` string, nullable
    - `email` string, email
    - `avatar_url` string, nullable: 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.
  - `entry_count` integer
  - `connectors` object: 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".
    - `total` integer: Distinct connector slugs across the template.
    - `preview` object[]: 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.
      - `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, nullable: 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, date-time
  - `updated_at` string, date-time
  - `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` 'view' | 'use' | 'edit' | 'null', nullable: Level of the org-wide grant, or null when there is none.
    - `team_count` integer
    - `user_count` integer
    - `total` integer: Every grant, the org-wide one included.
    - `preview` object[]: At most 5 grantees, broadest first (org, then teams, then members), for a hover preview.
      - `grantee_type` 'user' | 'team' | 'org'
      - `grantee_id` string, nullable
      - `level` 'view' | 'use' | 'edit'
      - `name` string, nullable: Display name; null for org grants and for grantees no longer in the org.
  - `shares` AclEntry[]: 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.
    - `id` string: Grant id (`acl_…`), the `{aclId}` that `DELETE .../share/{aclId}` takes.
    - `resource_type` 'connection' | 'toolbox' | 'template' | 'connector' | 'file'
    - `resource_id` string: The shared resource's id (a connector's slug, for that type).
    - `grantee_type` 'user' | 'team' | 'org': `user` and `team` grants target one `grantee_id`; `org` applies to every member of the organization and takes no id.
    - `grantee_id` string, nullable: User id (`usr_…`) or team id (`team_…`). Null for a `grantee_type: "org"` grant.
    - `level` 'view' | 'use' | 'edit': 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.
    - `created_at` string, date-time
    - `updated_at` string, date-time
  - `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 — based on the caller's ownership or grant level. Present on every list and detail row for both templates and toolboxes.
  - `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: 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}`.
  - `access_via` 'owner' | 'direct' | 'team' | 'org': 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`.
  - `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, nullable: Team display name, or null when the team no longer resolves in the directory — the same null contract every resolved grantee name carries.
  - `entries` object[]
    - `id` string: Entry id (`tple_…`) — stable across updates only if you send it back unchanged.
    - `type` 'proxy' | 'synthetic'
    - `synthetic_tool_id` string, nullable: Set for `synthetic` entries (`syn_…`).
    - `synthetic_tool_name` string, nullable: 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, nullable: Connector slug for `proxy` entries; null for synthetic.
    - `tool_name` string, nullable: Connector tool name for `proxy` entries.
    - `tool_title` string, nullable: 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, nullable: 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`.
      - `label` string, required
      - `logo` string, nullable, required
      - `upstream_slug` string: 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.
    - `can_view_connector` boolean: 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.
    - `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, nullable: 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, date-time, nullable: When `skill` was last written. Null alongside a null `skill`.
  - `skill_notice` string, nullable: 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, nullable: 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, nullable: 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, nullable: 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.
    - `added_count` integer
    - `removed_count` integer
    - `added` string[]: At most 5 names.
    - `removed` string[]: At most 5 names.
  - `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` 'assistant_disabled' | 'llm_not_configured' | 'null', nullable: 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.
  - `warnings` 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, required
    - `tool_name` string, required
    - `message` string, required

## Other responses

- `400`: Bad request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `429`: Rate limited

---

[API](https://skmtc.dev/elaichi/apis/elaichi-api.md) · [All operations](https://skmtc.dev/elaichi/apis/elaichi-api/llms.txt) · [OpenAPI document](https://skmtc.dev/elaichi/apis/elaichi-api/revisions/b22cfb7b6d15?raw)
