> ## Documentation Index
> Fetch the complete documentation index at: https://agent37.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Templates

> Name an image once, then create every instance from that name.

A template is a named image, plus (optionally) the port its main service listens on. Instances are always created from template names: pass `template` on [`POST /v1/instances`](/agents-api/instances), or omit it for the default `agent37-hermes`. Direct image references are rejected with `400 invalid_request`; register a template first, then pass its name.

```bash curl theme={null}
curl -X POST https://api.agent37.com/v1/instances \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "template": "agent37-hermes" }'
```

Templates come in two scopes. `system` templates are the built-in catalog, the same for every workspace and read-only. `workspace` templates are ones you register from your own image — a public registry reference or a [cloud build](/agents-api/custom-image#build-it-in-the-cloud) — visible only to your workspace.

<Note>
  Registering a workspace template copies your image once into private storage and pins it by digest. That snapshot is what instances run: registration takes up to a few minutes for a large image, the image is stored privately and never republished, and re-pushing the same registry tag later has no effect until you [update the template](#update-a-template).
</Note>

## System catalog

The `agent37-` prefix is reserved for system templates. The catalog has three entries today:

| Template               | What it runs                                                         |
| ---------------------- | -------------------------------------------------------------------- |
| `agent37-hermes`       | Hermes, the general agent: chat, browsing, code, files. The default. |
| `agent37-hermes-small` | Hermes, lean: chat, code, files, shell. No browser or desktop.       |
| `agent37-openclaw`     | OpenClaw, the general agent: chat, headless browsing, code, files.   |

`agent37-hermes-small` is the only template that can run on the sub-floor 1 vCPU / 3 GB shape, from \$3.44 per month; see [sizing](/agents-api/instances). Standard and custom templates start at 2 vCPU / 4 GB.

<Note>
  More system templates (Claude Code, Codex) are coming; they will appear in `GET /v1/templates` when they are available. Passing any other `agent37-` name on instance create returns `400 invalid_request`, because the prefix is reserved.
</Note>

On every system template, port `3737` is the gateway: the [chat API](/agents-api/chat) at `https://{instanceId}.agent37.app`. Any other port an instance serves — the built-in dashboard, terminal, and file browser included — is reachable at its derivable `https://{instanceId}-{port}.agent37.app` URL; see [Instance and preview URLs](/agents-api/urls).

System templates track the newest platform image: their `image_ref` moves forward as new versions ship, and each create uses whatever the template points at in that moment. Existing instances are unaffected — an instance keeps the image it was created from until you [update it](/agents-api/instances#update).

## Pin a template version

If you need every instance you create to be identical — say a setup script runs after each create and depends on exact paths or versions — pin the version: append `@<tag>` (system templates) or `@<revision>` (workspace templates) to the template name on create.

```bash curl theme={null}
curl -X POST https://api.agent37.com/v1/instances \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "template": "agent37-hermes@<tag>" }'
```

Every release is an immutable dated tag, never republished, so a pin is a freeze. The published tags are on GHCR: [hermes](https://github.com/orgs/agent37-platform/packages/container/package/hermes), [hermes-small](https://github.com/orgs/agent37-platform/packages/container/package/hermes-small), [openclaw](https://github.com/orgs/agent37-platform/packages/container/package/openclaw); the current tag is the one in each template's `image_ref` on `GET /v1/templates`. An unpublished tag returns `400 invalid_request`, and so does `@latest` — the bare name already follows the latest release. To check a version before creating from it, `GET /v1/templates/agent37-hermes@<tag>`: it returns the template with the pinned `image_ref`, or `404` if that version isn't published.

Workspace templates pin by revision number: every image publish (a changed `image_ref` or a successful [cloud build](#build-an-image-in-the-cloud)) appends a release, `GET /v1/templates/{name}` lists them under [`revisions`](#the-template-object), and `my-agent@2` freezes revision `2`. `GET /v1/templates/my-agent@2` probes one the same way as a system tag — `404` if it was never published — and an unpublished revision on create or update returns `400 invalid_request` naming the published ones.

The pin sticks: the instance object's `template` field carries it (`"agent37-hermes@2026.07.02b"`, `"my-agent@2"`), and every lifecycle operation, including [`update`](/agents-api/instances#update), stays on the pinned version. To pin an existing instance, move its pin, or clear it, pass a `template` to update: the name with an `@<tag>` or `@<revision>` sets or switches the pin, the bare name clears it and follows latest again.

## Endpoints

| Method   | Path                             | Returns                                                                                       |
| -------- | -------------------------------- | --------------------------------------------------------------------------------------------- |
| `GET`    | `/v1/templates`                  | `200` `{ "data": [...] }`, system catalog first, then your workspace templates sorted by name |
| `POST`   | `/v1/templates`                  | `201` with the new workspace template                                                         |
| `GET`    | `/v1/templates/{name}`           | `200` with one template, system or workspace                                                  |
| `PATCH`  | `/v1/templates/{name}`           | `200` with the updated workspace template                                                     |
| `DELETE` | `/v1/templates/{name}`           | `200` `{ "name": "...", "deleted": true }`                                                    |
| `POST`   | `/v1/template-builds`            | `201` with a new cloud build and a presigned URL for its build context                        |
| `POST`   | `/v1/template-builds/{id}/start` | `200` with the build now `building`                                                           |
| `GET`    | `/v1/template-builds/{id}`       | `200` with the build, including the published template once it succeeds                       |
| `GET`    | `/v1/template-builds/{id}/logs`  | `200` with the build plus an incremental chunk of its build log                               |

```bash curl theme={null}
curl https://api.agent37.com/v1/templates \
  -H "Authorization: Bearer sk_live_..."
```

## Build an image in the cloud

To publish a local or private image, you don't upload the image — you upload a small **build context** (a `Dockerfile` plus the files it `COPY`s) and Agent37 builds the image on its own infrastructure. No local Docker needed, and nothing has to be public except what the build fetches. The supported path is the Agent37 CLI:

```bash theme={null}
export AGENT37_API_KEY=sk_live_...
npx agent37 templates build ./my-agent --name my-agent
```

The directory defaults to `.` and must have a `Dockerfile` at its root. The CLI packs the **whole folder** — minus `.git` and your `.dockerignore` patterns (plain patterns only; `!` negations are ignored) — gzips it (100 MB cap), and streams the live build log to your terminal; on failure it exits non-zero with the failing step visible. Every unexcluded file ships with the context, a stray `.env` included, and a `COPY . .` bakes it into the image — check the folder, or add a `.dockerignore`, before you build. `--name` defaults to the folder name, and `--default-port <port>` sets the published template's default port. Re-building an existing name publishes a new template revision; existing instances never change. Ctrl-C does not cancel the build — it keeps running server-side.

See [Build a custom image](/agents-api/custom-image) for the walkthrough. The raw flow below is for clients scripting the same thing without the CLI.

### 1. Create a build

`POST /v1/template-builds` with the template name and the exact byte count of your gzipped context:

<ParamField body="name" type="string" required>
  The template the build publishes, following the same rules as [template names](#register-a-workspace-template). A new name is created at revision `1` when the build succeeds; an existing name gets a new revision.
</ParamField>

<ParamField body="size_bytes" type="integer" required>
  Exact size of the gzipped context, from `1` through `100,000,000` bytes (100 MB).
</ParamField>

<ParamField body="default_port" type="integer">
  Applied to the published template, with the same semantics as on [`POST /v1/templates`](#register-a-workspace-template).
</ParamField>

<ParamField body="description" type="string">
  Optional free-text description for the published template.
</ParamField>

```bash curl theme={null}
tar --exclude-vcs -czf context.tar.gz -C ./my-agent .
BYTES=$(wc -c < context.tar.gz | tr -d ' ')

curl --fail-with-body -sS -X POST https://api.agent37.com/v1/template-builds \
  -H "Authorization: Bearer $AGENT37_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"my-agent\",\"size_bytes\":$BYTES}" > build.json
```

```json response theme={null}
{
  "id": "tb_7d3a90b81f264bc2a113",
  "name": "my-agent",
  "status": "created",
  "upload_url": "https://uploads.example.com/...signed...",
  "expires_at": 1784192400,
  "max_bytes": 100000000
}
```

<ResponseField name="id" type="string">
  The workspace-scoped build id, `tb_` followed by 20 lowercase hex characters.
</ResponseField>

<ResponseField name="upload_url" type="string">
  A presigned `PUT` URL for the gzipped context, valid for one hour. It is a bearer credential: send no Agent37 key to it, and do not log or share it.
</ResponseField>

<ResponseField name="expires_at" type="integer">
  When `upload_url` expires, in epoch seconds.
</ResponseField>

<ResponseField name="max_bytes" type="integer">
  The context cap: exactly `100,000,000` bytes (100 MB).
</ResponseField>

### 2. Upload the context

`PUT` the gzipped tar to `upload_url`. The archive must have `Dockerfile` at its root — pack the folder's contents, not the folder. Clear curl's default `Expect: 100-continue` header; it is incompatible with these presigned uploads:

```bash curl theme={null}
curl --fail-with-body -X PUT -H 'Expect:' \
  --data-binary @context.tar.gz "$(jq -r '.upload_url' build.json)"
```

### 3. Start the build

```bash curl theme={null}
BUILD_ID=$(jq -r '.id' build.json)

curl --fail-with-body -sS -X POST \
  "https://api.agent37.com/v1/template-builds/$BUILD_ID/start" \
  -H "Authorization: Bearer $AGENT37_API_KEY"
```

```json response theme={null}
{ "id": "tb_7d3a90b81f264bc2a113", "status": "building" }
```

Starting a build whose context was never uploaded, or is over 100 MB, returns `400 invalid_request`. One build runs at a time per workspace: a build that already started, or a start while another build is running, returns `409 build_conflict`.

### 4. Poll it and tail the log

`GET /v1/template-builds/{id}` returns the build; `GET /v1/template-builds/{id}/logs?offset=N` returns the same fields plus a chunk of build log starting at `offset` — pass the returned `offset` back to tail incrementally. Poll rather than wait silently: the poll that finds the build finished is what triggers template registration (an unpolled build is still completed by a platform sweep within about five minutes).

```bash curl theme={null}
curl -sS "https://api.agent37.com/v1/template-builds/$BUILD_ID/logs?offset=0" \
  -H "Authorization: Bearer $AGENT37_API_KEY"
```

```json response theme={null}
{
  "id": "tb_7d3a90b81f264bc2a113",
  "name": "my-agent",
  "status": "succeeded",
  "template": {
    "name": "my-agent",
    "revision": 1,
    "image_digest": "sha256:9b2e...c41f"
  },
  "created": 1784190000,
  "started": 1784190010,
  "finished": 1784190190,
  "offset": 5120,
  "data": "#8 exporting layers\n#8 DONE 1.2s\n"
}
```

<ResponseField name="status" type="string">
  `created` (waiting for the context and `start`), `building` (the Docker build is running), `ingesting` (build done; the image is being copied into private storage), `succeeded`, or `failed`.
</ResponseField>

<ResponseField name="error" type="object">
  On `failed`, the reason as `{ "code", "message" }`. See [the failure codes](#build-limits-and-failures).
</ResponseField>

<ResponseField name="template" type="object">
  On `succeeded`, the published result: the template `name`, its new `revision`, and the pinned `image_digest`.
</ResponseField>

<ResponseField name="created" type="integer">
  When the build was created, in epoch seconds.
</ResponseField>

<ResponseField name="started" type="integer">
  When the build started, in epoch seconds. Set once it starts.
</ResponseField>

<ResponseField name="finished" type="integer">
  When the build finished, in epoch seconds. Set once it succeeds or fails.
</ResponseField>

<ResponseField name="offset" type="integer">
  Logs endpoint only: the next offset to request.
</ResponseField>

<ResponseField name="data" type="string">
  Logs endpoint only: the log chunk starting at your offset — the real Docker build output. It stays readable after the build finishes.
</ResponseField>

### Build limits and failures

Builds are free. One build runs at a time per workspace, a build times out after 45 minutes, and the gzipped context is capped at 100 MB. The built image is capped at 8 GB decimal like a registry import, though built images practically top out around 4 GB today; if you need more, [talk to the team](https://cal.com/vishnukool/30min).

Everything the build fetches — the `FROM` base image, anything `RUN` downloads — must be **publicly reachable**. There is no build-secret or private-registry-credential support yet; for a private base image, [contact support](mailto:support@agent37.com). A template published by a build has an `image_digest` and no `image_ref`, because no public reference exists.

A failed build carries its reason in `error.code`:

| Code                    | What happened                                                                                                                     |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `build_failed`          | A Dockerfile step failed; the failing step is at the end of the build log                                                         |
| `build_timeout`         | The build ran past the 45-minute limit                                                                                            |
| `invalid_request`       | The context is unusable: not a gzipped tar, or no `Dockerfile` at its root                                                        |
| `invalid_image_archive` | The built image failed validation: not a readable single `linux/amd64` image                                                      |
| `image_too_large`       | The built image is over the 8 GB decimal cap                                                                                      |
| `image_ingest_failed`   | The copy into private storage failed for a reason on our side; re-run the build, or [contact support](mailto:support@agent37.com) |
| `internal_error`        | The build failed for a reason on our side; re-run the build, or [contact support](mailto:support@agent37.com)                     |

## Register a workspace template

`POST /v1/templates` registers an amd64 image under a name you choose. The platform runs it as a managed instance with the same lifecycle, billing, [exec](/agents-api/exec), and routed port URLs as the catalog; what the image serves on its ports is up to it. The image can be anything, built from scratch or on [the Hermes base image](#build-on-the-hermes-base-image). Give the image as an `image_ref`, a **public** registry reference; for an image that isn't public, use a [cloud build](#build-an-image-in-the-cloud) instead — it publishes the template itself, so there is nothing to register afterward. If you need an image larger than the 8 GB cap allows, [talk to the team](https://cal.com/vishnukool/30min).

<ParamField body="name" type="string" required>
  2 to 63 characters: lowercase letters, digits, and hyphens, starting with a letter (`^[a-z][a-z0-9-]{1,62}$`). The `agent37-` prefix is reserved. A name that already exists returns `409 template_conflict`.
</ParamField>

<ParamField body="image_ref" type="string" required>
  A fully qualified **public** image reference with a registry or namespace path, like `ghcr.io/acme/my-agent:v1`. Up to 255 characters. The image must be public (so the platform can copy it), built for `linux/amd64`, and at most 8 GB. On registration the platform pulls it once into private storage and pins it by digest; a reference that isn't publicly pullable, isn't `linux/amd64`, or is over the cap is rejected then.
</ParamField>

<ParamField body="description" type="string">
  Optional free-text description.
</ParamField>

<ParamField body="default_port" type="integer">
  The port the bare instance URL (`https://{instanceId}.agent37.app`) routes to, and the port the platform probes at boot to decide the create succeeded. Your service must bind it on `0.0.0.0`, not only localhost. An integer 1 to 65535; the reserved ports `7681`, `8080`, `6080`, `7890`, `9119` are rejected (they belong to the managed runtime). Omitted, the bare URL falls back to `3737` — right for images built on [the Hermes base image](#build-on-the-hermes-base-image), whose gateway serves it — and no TCP boot probe runs. A private sandbox can listen on nothing and be driven by [exec](/agents-api/exec), but its main process must still stay running. Every other port needs no declaration: it is already reachable at `https://{instanceId}-{port}.agent37.app`. With [auto-sleep](/agents-api/instances#auto-sleep) on, a sandbox nothing ever requests only wakes by an explicit [`start`](/agents-api/instances#start).
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.agent37.com/v1/templates \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "name": "my-agent",
      "image_ref": "ghcr.io/acme/my-agent:v1",
      "description": "My agent",
      "default_port": 8000
    }'
  ```

  ```python python theme={null}
  import requests

  resp = requests.post(
      "https://api.agent37.com/v1/templates",
      headers={"Authorization": "Bearer sk_live_..."},
      json={
          "name": "my-agent",
          "image_ref": "ghcr.io/acme/my-agent:v1",
          "description": "My agent",
          "default_port": 8000,
      },
  )
  print(resp.json())
  ```

  ```javascript node theme={null}
  const resp = await fetch("https://api.agent37.com/v1/templates", {
    method: "POST",
    headers: {
      Authorization: "Bearer sk_live_...",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "my-agent",
      image_ref: "ghcr.io/acme/my-agent:v1",
      description: "My agent",
      default_port: 8000,
    }),
  });
  console.log(await resp.json());
  ```
</CodeGroup>

Registration copies the image into private storage before it returns, so `POST` and `PATCH` are synchronous and can take up to a few minutes for a large image. A reference that isn't publicly pullable, isn't `linux/amd64`, or is over the cap is rejected at registration — `400 invalid_request` or `400 image_too_large` — so a broken image never reaches instance create.

<Tip>
  Pin a tag on `image_ref`; do not rely on `latest`. A pinned tag makes rollback a one-field `PATCH` and keeps every instance you create reproducible. The template is a snapshot regardless — pinning is about which build you re-register next.
</Tip>

<Note>
  Build for `linux/amd64` even on an Apple Silicon Mac (`docker build --platform linux/amd64 ...`). An arm64 image is rejected during registration.
</Note>

## Build on the Hermes base image

[`ghcr.io/agent37-platform/hermes-base`](https://github.com/orgs/agent37-platform/packages/container/package/hermes-base) is the published FROM target for custom templates. It ships Hermes with its browser stack (Chromium, Playwright), the gateway that serves the [chat API](/agents-api/chat), and a general toolchain: git, Python 3 with uv, Node.js, build tools. There are no platform LLM credentials and no managed search or Composio integrations; you add your layers on top and bring your own model keys.

```dockerfile theme={null}
FROM ghcr.io/agent37-platform/hermes-base:latest

USER root
RUN apt-get update && apt-get install -y postgresql-client
COPY my-skills/ /usr/local/share/agent37/default-skills/
USER node
```

Publish it with a [cloud build](#build-an-image-in-the-cloud) — `npx agent37 templates build` from the folder holding the Dockerfile — or build it yourself for `linux/amd64` and push it to a public registry. OpenClaw builds have the same shape: the FROM target is [`ghcr.io/agent37-platform/openclaw-base`](https://github.com/orgs/agent37-platform/packages/container/package/openclaw-base), published with the same dated tags as `agent37-openclaw`.

See [Build a custom image](/agents-api/custom-image) for the step-by-step walkthrough, with a copy-able example — a `Dockerfile`, an example skill, a registration script, and a tiny bring-your-own-model proxy.

The contract:

* Your built image freezes the base it was built from; the FROM tag only matters when you rebuild. `latest` tracks the newest base, so getting-started builds never go stale. For reproducible rebuilds, pin a date tag: `hermes-base` publishes the same dated tags as `agent37-hermes` ([list on GHCR](https://github.com/orgs/agent37-platform/packages/container/package/hermes-base)), and the current one is the tag in `agent37-hermes`'s `image_ref` on `GET /v1/templates`.
* Keep the entrypoint. It starts Hermes and the gateway; a Dockerfile that overrides `ENTRYPOINT` loses the chat API.
* Bake outside `/home/node` and `/home/linuxbrew`. Both are persistent volumes, so anything the image writes there is masked at runtime. Use `/usr/local` or `/opt`. Skills placed in `/usr/local/share/agent37/default-skills/` are copied into `~/.hermes/skills` at boot.
* The image runs as the `node` user, with passwordless sudo. Switch to `USER root` for installs and back to `USER node` at the end.
* Leave the default port to the gateway. The runtime tells the gateway which port to bind — `default_port`, or `3737` when you omit it — so the bare instance URL serves the chat API; do not bind your own service there. Your own services need no declaration: each is reachable at `https://{instanceId}-{port}.agent37.app`.
* Bring your own model keys. Instances boot with no LLM provider configured, and chat returns errors until you add one. Write your provider credentials into `~/.hermes/config.yaml`, Hermes' standard config file, over [exec](/agents-api/exec) or the terminal. The file lives on the persistent volume, so it survives restarts and updates.

## Update a template

`PATCH /v1/templates/{name}` changes any of `name`, `image_ref`, `description`, or `default_port` (send `null` to clear it back to the `3737` fallback). Send at least one field. A changed `image_ref` is copied into private storage, so an image update is synchronous like create, and each successful image update increments the platform-controlled `revision`. Sending the exact same `image_ref` string reuses the existing snapshot and does not increment it; renaming or changing only `description` or `default_port` also leaves it unchanged. To ship a new image on a template published by a [cloud build](#build-an-image-in-the-cloud) (no `image_ref`), run a new build under the same name — every successful build publishes a new revision, even when the built image's digest is unchanged. Renaming onto an existing name returns `409 template_conflict`; system templates return `403`.

```bash curl theme={null}
curl -X PATCH https://api.agent37.com/v1/templates/my-agent \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "image_ref": "ghcr.io/acme/my-agent:v2" }'
```

To roll back, [pin instances to the earlier revision](#pin-a-template-version) — `POST /v1/instances/{id}/update` with `{ "template": "my-agent@1" }` — no re-publish needed. To make an old release the template's *current* one again, point `image_ref` at the older tag (or, for a build-born template, re-run the [build](#build-an-image-in-the-cloud) from the older source); that publishes it as a new revision. Existing instances keep running on the image they already pulled; [update each instance](/agents-api/instances) to recreate it from the template's current image.

Workspace templates start at `revision: 1`. Compare that value with an instance's `template_revision` to find instances that have not installed the current template release.

## Roll out a release to your fleet

After an image update, list your instances and [update](/agents-api/instances#update) the ones still on an older revision. An instance's `template_revision` is what it has installed; the template's `revision` is the current release. Missing instance revisions read as `1`.

```bash curl theme={null}
TEMPLATE=my-agent
REV=$(curl -s https://api.agent37.com/v1/templates/$TEMPLATE \
  -H "Authorization: Bearer sk_live_..." | jq .revision)

curl -s https://api.agent37.com/v1/instances \
  -H "Authorization: Bearer sk_live_..." |
  jq -r --arg t "$TEMPLATE" --argjson r "$REV" \
    '.data[] | select(.template == $t and (.template_revision // 1) != $r) | .id' |
  head -n 3 |
  xargs -I{} curl -s -X POST "https://api.agent37.com/v1/instances/{}/update" \
    -H "Authorization: Bearer sk_live_..."
```

The `head -n 3` is the canary: update a small slice first, verify the release on those instances, then rerun without it to finish the fleet. Rerunning is always safe, the filter only picks up instances still behind. A `sleeping` instance cannot be updated; wake it with [`start`](/agents-api/instances#start) first.

## Delete a template

`DELETE /v1/templates/{name}` removes the workspace template and acts once: the first call returns `200` with `{ "name": "my-agent", "deleted": true }`, and a repeat returns `404`. System templates return `403`. Existing instances created from it keep running, so deletion does not immediately purge the private image copy they still reference. Contact support if you need the stored image purged after its instances are gone.

```bash curl theme={null}
curl -X DELETE https://api.agent37.com/v1/templates/my-agent \
  -H "Authorization: Bearer sk_live_..."
```

## The template object

```json response theme={null}
{
  "name": "agent37-hermes",
  "scope": "system",
  "agents": ["hermes"],
  "revision": null,
  "image_ref": "ghcr.io/agent37-platform/hermes:2026.07.02b",
  "description": "Hermes general agent: browser, code, files.",
  "default_port": 3737,
  "created": null,
  "updated": null
}
```

A workspace template registered from a public reference echoes that reference as `image_ref` and adds the pinned `image_digest`; one published by a [cloud build](#build-an-image-in-the-cloud) has an `image_digest` and no `image_ref`:

```json response theme={null}
{
  "name": "my-agent",
  "scope": "workspace",
  "agents": [],
  "revision": 2,
  "image_digest": "sha256:9b2e...c41f",
  "description": "My agent",
  "default_port": 8000,
  "created": 1752460800,
  "updated": 1752547200,
  "revisions": [
    { "revision": 2, "image_digest": "sha256:9b2e...c41f", "created": 1752547200 },
    { "revision": 1, "image_digest": "sha256:41d8...77aa", "created": 1752460800 }
  ]
}
```

<ResponseField name="name" type="string">
  The name you pass as `template` when creating an instance.
</ResponseField>

<ResponseField name="scope" type="string">
  `system` for built-in `agent37-` templates, `workspace` for ones you register.
</ResponseField>

<ResponseField name="agents" type="string[]">
  The agents the template installs: `["hermes"]` on `agent37-hermes`. Workspace templates report `[]` — the platform does not inspect what your image installs.
</ResponseField>

<ResponseField name="revision" type="integer | null">
  The platform-controlled workspace image revision. It starts at `1` and increments for a changed `image_ref` or every successful [cloud build](#build-an-image-in-the-cloud), even when the digest is unchanged. Metadata-only updates and the same exact `image_ref` leave it unchanged. `null` for system templates.
</ResponseField>

<ResponseField name="revisions" type="array">
  Every published release of a workspace template, newest first: `{ "revision": 2, "image_digest": "sha256:...", "created": 1752547200 }`. [Pin one](#pin-a-template-version) with `name@<revision>` on instance create or update. Returned on `GET /v1/templates/{name}` only; absent from the list endpoint and from system templates. Templates that predate release history list only the revisions published since.
</ResponseField>

<ResponseField name="image_ref" type="string">
  For system templates and workspace templates registered from a registry, the image reference. **Absent** on a workspace template published by a [cloud build](#build-an-image-in-the-cloud) — its image lives only in Agent37's private storage, identified by `image_digest`.
</ResponseField>

<ResponseField name="image_digest" type="string">
  On workspace templates, the `sha256:...` digest of the private snapshot instances run — the immutable identity of the registered image. Absent on system templates.
</ResponseField>

<ResponseField name="description" type="string | null">
  Your optional description.
</ResponseField>

<ResponseField name="default_port" type="integer | null">
  The declared default port, or `null` when the template relies on the `3737` fallback. See [Instance and preview URLs](/agents-api/urls).
</ResponseField>

<ResponseField name="created" type="integer | null">
  Creation time in epoch seconds. `null` for system templates.
</ResponseField>

<ResponseField name="updated" type="integer | null">
  Last update time in epoch seconds. `null` for system templates.
</ResponseField>

## Errors

| Status | Code                  | When                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_request`     | Bad `name`, `image_ref`, or `default_port`; a template create with no `image_ref`; a not-publicly-pullable registry reference; the legacy `ports` array; a direct image reference passed as `template` on instance create; a bad [pin](#pin-a-template-version) on create or update (unpublished system tag, `@latest`, non-numeric or unpublished workspace revision); a [build](#build-an-image-in-the-cloud) `start` with no uploaded context or one over 100 MB |
| `400`  | `image_too_large`     | The staged image is over the 8 GB decimal cap                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `403`  | `forbidden`           | `PATCH` or `DELETE` on a system template: they are read-only                                                                                                                                                                                                                                                                                                                                                                                                        |
| `404`  | `not_found`           | Unknown template name or build id; a pinned `GET` for an unpublished version or revision; repeat `DELETE`                                                                                                                                                                                                                                                                                                                                                           |
| `409`  | `template_conflict`   | Create or rename onto a name that already exists                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `409`  | `build_conflict`      | A build `start` on a build that already started, or while another build is running in your workspace — one build runs at a time                                                                                                                                                                                                                                                                                                                                     |
| `502`  | `image_ingest_failed` | The copy into private storage failed for a reason on our side; retry, or [contact support](mailto:support@agent37.com)                                                                                                                                                                                                                                                                                                                                              |

A build that fails after starting is not an HTTP error: `GET /v1/template-builds/{id}` returns `200` with `status: "failed"` and the reason in its `error` field — see [build limits and failures](#build-limits-and-failures).

See [Errors](/agents-api/errors) for the envelope and the full catalog.
