message.comDevelopers

Saved replies Live

Saved replies are workspace-shared templates that agents insert in one keystroke. Also called macros or canned responses elsewhere. Each carries a shortcut, a title, a body that supports handlebars-style placeholders, and a channel scope.

List saved replies

GET/api/v1/saved-repliesAuth: Bearer

Cursor-paginated. Filters: scope (workspace / personal), channel, q (full-text on title and body).

Code samples

cURL
curl 'https://app.message.com/api/v1/saved-replies' \
  -H 'Authorization: Bearer YOUR_WORKSPACE_JWT'
JavaScript
const res = await fetch('https://app.message.com/api/v1/saved-replies', {
  headers: { Authorization: 'Bearer ' + token }
});
const { savedReplies } = await res.json();
Python
import requests
r = requests.get(
    "https://app.message.com/api/v1/saved-replies",
    headers={"Authorization": f"Bearer {token}"},
)
saved = r.json()["savedReplies"]
Ruby
require "net/http"
require "json"
uri = URI("https://app.message.com/api/v1/saved-replies")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  req = Net::HTTP::Get.new(uri)
  req["Authorization"] = "Bearer #{token}"
  http.request(req)
end
saved = JSON.parse(res.body)["savedReplies"]
PHP
<?php
$ctx = stream_context_create([
  "http" => ["method" => "GET", "header" => "Authorization: Bearer $token"]
]);
$saved = json_decode(file_get_contents("https://app.message.com/api/v1/saved-replies", false, $ctx), true)["savedReplies"];

Get one saved reply

GET/api/v1/saved-replies/:idAuth: Bearer
200 OK
{
  "id": "uuid",
  "version": 1,
  "shortcut": "/refund",
  "title": "Refund policy",
  "body": "Hi {{visitor.name}}, our refund policy is...",
  "channels": ["chat", "ticket"],
  "scope": "workspace",
  "createdByAgentId": "uuid",
  "createdAt": "2026-05-13T15:30:00Z"
}

Create a saved reply

POST/api/v1/saved-repliesAuth: Bearer
FieldTypeDescription
shortcutrequiredstringSlash-prefixed keystroke. 2 to 32 chars. Must be unique within scope.
titlerequiredstringDisplay title shown in the composer dropdown.
bodyrequiredstringReply body. Supports {{visitor.name}}, {{agent.name}}, {{workspace.name}}.
channelsoptionalstring[]Subset of chat, ticket. Defaults to both.
scopeoptionalenumOne of workspace (shared) or personal (current agent only). Defaults to workspace for admins, personal for agents.
Body
{
  "shortcut": "/refund",
  "title": "Refund policy",
  "body": "Hi {{visitor.name}}, our refund policy is...",
  "channels": ["chat", "ticket"],
  "scope": "workspace"
}

Update a saved reply

PATCH/api/v1/saved-replies/:idAuth: Bearer

Edit shortcut, title, body, channels, or scope. Requires expectedVersion. Agents can edit their personal replies; only admins can edit workspace-scoped ones.

Delete a saved reply

DELETE/api/v1/saved-replies/:idAuth: Bearer

Placeholder substitution

The body supports Handlebars-style substitution. Resolved at insertion time:

PlaceholderResolves to
{{visitor.name}}The visitor's name, or "there" if unknown.
{{visitor.email}}The visitor's email if identified.
{{visitor.firstName}}First whitespace-split token of name.
{{agent.name}}The inserting agent's display name.
{{workspace.name}}The workspace display name.
{{conversation.subject}}The conversation subject (tickets only).

Unknown placeholders pass through as literal text. There is no error if you reference a field that does not exist.

Errors

CodeWhen
400 invalid_bodyShortcut format invalid or body too long.
403 forbiddenAgent cannot edit a workspace-scoped reply.
409 conflictShortcut already used within scope.
409 stale_versionexpectedVersion mismatch.

Common pitfalls

  • Sharing a personal reply with the team. Personal replies do not migrate. Recreate as workspace-scoped.
  • Hardcoding the agent name into the body. Use {{agent.name}} so the same reply works for everyone.
  • Treating saved replies as the same as saved views. Different things: saved replies populate the composer; saved views filter the queue.