Skip to main content
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, 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.
curl
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 — visible only to your workspace.
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.

System catalog

The agent37- prefix is reserved for system templates. The catalog has three entries today: 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. Standard and custom templates start at 2 vCPU / 4 GB.
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.
On every system template, port 3737 is the gateway: the chat API 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. 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.

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.
curl
Every release is an immutable dated tag, never republished, so a pin is a freeze. The published tags are on GHCR: hermes, hermes-small, 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) appends a release, GET /v1/templates/{name} lists them under revisions, 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, 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

curl

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 COPYs) 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:
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 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:
name
string
required
The template the build publishes, following the same rules as template names. A new name is created at revision 1 when the build succeeds; an existing name gets a new revision.
size_bytes
integer
required
Exact size of the gzipped context, from 1 through 100,000,000 bytes (100 MB).
default_port
integer
Applied to the published template, with the same semantics as on POST /v1/templates.
description
string
Optional free-text description for the published template.
curl
response
id
string
The workspace-scoped build id, tb_ followed by 20 lowercase hex characters.
upload_url
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.
expires_at
integer
When upload_url expires, in epoch seconds.
max_bytes
integer
The context cap: exactly 100,000,000 bytes (100 MB).

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:
curl

3. Start the build

curl
response
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).
curl
response
status
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.
error
object
On failed, the reason as { "code", "message" }. See the failure codes.
template
object
On succeeded, the published result: the template name, its new revision, and the pinned image_digest.
created
integer
When the build was created, in epoch seconds.
started
integer
When the build started, in epoch seconds. Set once it starts.
finished
integer
When the build finished, in epoch seconds. Set once it succeeds or fails.
offset
integer
Logs endpoint only: the next offset to request.
data
string
Logs endpoint only: the log chunk starting at your offset — the real Docker build output. It stays readable after the build finishes.

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. 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. 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:

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, 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. Give the image as an image_ref, a public registry reference; for an image that isn’t public, use a cloud build 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.
name
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.
image_ref
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.
description
string
Optional free-text description.
default_port
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, whose gateway serves it — and no TCP boot probe runs. A private sandbox can listen on nothing and be driven by 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 on, a sandbox nothing ever requests only wakes by an explicit start.
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.
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.
Build for linux/amd64 even on an Apple Silicon Mac (docker build --platform linux/amd64 ...). An arm64 image is rejected during registration.

Build on the Hermes base image

ghcr.io/agent37-platform/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, 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.
Publish it with a cloud buildnpx 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, published with the same dated tags as agent37-openclaw. See Build a 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), 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 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 (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.
curl
To roll back, pin instances to the earlier revisionPOST /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 from the older source); that publishes it as a new revision. Existing instances keep running on the image they already pulled; update each instance 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 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.
curl
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 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.
curl

The template object

response
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 has an image_digest and no image_ref:
response
name
string
The name you pass as template when creating an instance.
scope
string
system for built-in agent37- templates, workspace for ones you register.
agents
string[]
The agents the template installs: ["hermes"] on agent37-hermes. Workspace templates report [] — the platform does not inspect what your image installs.
revision
integer | null
The platform-controlled workspace image revision. It starts at 1 and increments for a changed image_ref or every successful cloud build, even when the digest is unchanged. Metadata-only updates and the same exact image_ref leave it unchanged. null for system templates.
revisions
array
Every published release of a workspace template, newest first: { "revision": 2, "image_digest": "sha256:...", "created": 1752547200 }. Pin one 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.
image_ref
string
For system templates and workspace templates registered from a registry, the image reference. Absent on a workspace template published by a cloud build — its image lives only in Agent37’s private storage, identified by image_digest.
image_digest
string
On workspace templates, the sha256:... digest of the private snapshot instances run — the immutable identity of the registered image. Absent on system templates.
description
string | null
Your optional description.
default_port
integer | null
The declared default port, or null when the template relies on the 3737 fallback. See Instance and preview URLs.
created
integer | null
Creation time in epoch seconds. null for system templates.
updated
integer | null
Last update time in epoch seconds. null for system templates.

Errors

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. See Errors for the envelope and the full catalog.