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
| Field | Type | Description |
|---|---|---|
| shortcutrequired | string | Slash-prefixed keystroke. 2 to 32 chars. Must be unique within scope. |
| titlerequired | string | Display title shown in the composer dropdown. |
| bodyrequired | string | Reply body. Supports {{visitor.name}}, {{agent.name}}, {{workspace.name}}. |
| channelsoptional | string[] | Subset of chat, ticket. Defaults to both. |
| scopeoptional | enum | One 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:
| Placeholder | Resolves 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
| Code | When |
|---|---|
400 invalid_body | Shortcut format invalid or body too long. |
403 forbidden | Agent cannot edit a workspace-scoped reply. |
409 conflict | Shortcut already used within scope. |
409 stale_version | expectedVersion 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.