Skip to main content

Macros, Tags & Routing

Canned replies with placeholders and side-effect actions, a tag vocabulary, and a first-match-wins routing engine with a dry-run evaluator.

Overview

Three operational primitives sit behind the support desk:

  • Macros — canned replies. A macro carries a body with {{placeholder}} variables and, optionally, actions that are applied besides sending the text: set a status, add tags, assign a user.
  • Tags — your tenant's tag vocabulary for tickets: a name, a display colour and a description.
  • Routing rules — an ordered, first-match-wins triage list. Each rule ANDs a set of conditions over a fixed six-field allowlist and, when it matches, applies actions.

Authentication

Authorization: Bearer cm_your_api_key
OperationScope
Every list/read, plus POST /routing-rules/evaluatesupport_ops:read
Every create, update, delete, plus POST /macros/{id}/usesupport_ops:write

POST /routing-rules/evaluate is a dry run — it writes nothing, so it needs only the read scope. POST /macros/{id}/use increments a counter, so it needs write.


Macros

The macro object

{
  "id": "9b8a…",
  "tenant_id": "a0b1…",
  "name": "Refund acknowledged",
  "body": "Hi {{customer_name}}, your refund for order {{order_id}} is on its way.",
  "actions": { "set_status": "pending", "add_tags": ["refund"], "assign_to": null },
  "category": "billing",
  "is_active": true,
  "usage_count": 41,
  "created_at": "2026-08-01T10:00:00Z",
  "updated_at": "2026-08-16T14:00:00Z"
}

actions

FieldTypeConstraints
set_statusstring | nullAt most 32 characters, lowercase identifier
add_tagsstring[] | nullAt most 20, each at most 64 characters, non-blank
assign_toUUID | nullMust be a user in your tenant

Unknown keys inside actions are rejected with 422 rather than ignored, so a typo cannot silently do nothing.

GET /api/v1/support/ops/macros

Ordered by usage_count descending, then name — so the picker shows what your team actually uses.

ParameterTypeConstraints
categorystringAt most 64 characters, exact match
is_activeboolean
limitinteger1 <= limit <= 200, default 50
offsetinteger0 <= offset <= 100000, default 0

POST /api/v1/support/ops/macros

FieldTypeRequiredConstraints
namestringYes1–255 characters, not blank, unique per tenant
bodystringYes1–20,000 characters, not blank
actionsobjectNoSee above
categorystringNoAt most 64 characters
is_activebooleanNoDefault true

409 A macro named '…' already exists on a duplicate name.

PATCH / DELETE /api/v1/support/ops/macros/{macro_id}

PATCH takes the same fields, all optional; sending actions: null clears the actions. DELETE returns 204.

POST /api/v1/support/ops/macros/{macro_id}/use

Renders the macro against a context and bumps usage_count.

FieldTypeRequiredConstraints
contextobjectNoDefault {}. At most 50 keys; at most 16,000 characters serialised
curl -X POST https://api.callmissed.com/api/v1/support/ops/macros/9b8a…/use \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "context": { "customer_name": "Asha", "order_id": "A-4419" } }'
{
  "id": "9b8a…",
  "name": "Refund acknowledged",
  "body": "Hi Asha, your refund for order A-4419 is on its way.",
  "actions": { "set_status": "pending", "add_tags": ["refund"] },
  "usage_count": 42
}

Placeholder syntax is {{name}}, where the name starts with a letter or underscore. Values are truncated to 500 characters.

Unknown or null-valued placeholders are left verbatim — {{order_id}} stays in the text rather than becoming an empty gap. Check the rendered body before sending it to a customer, or supply every variable the macro declares.

Applying the returned actions is your job: this endpoint renders and counts, it does not mutate a ticket.


Tags

{
  "id": "7f6e…",
  "tenant_id": "a0b1…",
  "name": "refund",
  "color": "#c2410c",
  "description": "Money going back to the customer",
  "created_at": "2026-08-01T10:00:00Z",
  "updated_at": "2026-08-01T10:00:00Z"
}
MethodPathScope
GET/api/v1/support/ops/tagssupport_ops:read
POST/api/v1/support/ops/tagssupport_ops:write
PATCH/api/v1/support/ops/tags/{tag_id}support_ops:write
DELETE/api/v1/support/ops/tags/{tag_id}support_ops:write

GET is ordered by name and takes limit (1..500, default 100) and offset (0..100000). There are no filters.

FieldTypeRequiredConstraints
namestringYes1–64 characters, not blank, unique per tenant
colorstringNoA hex triplet such as #c2410c or #c40. Stored lowercase
descriptionstringNoAt most 255 characters

Anything other than a hex triplet returns 422 color must be a hex triplet like '#aabbcc'.


Routing rules

Vocabulary

ConceptAllowed values
Condition fieldchannel, priority, status, subject_contains, contact_email_domain, tag
Condition operatoreq, neq, contains, in, is_set, is_not_set
assign_strategydirect, round_robin

The field list is a closed allowlist — an unknown field is 422, not a silently-false condition.

Condition value rules

Operatorvalue
inA non-empty array, at most 100 entries
is_set / is_not_setOmitted
Everything elseA non-blank scalar (string at most 512 characters, number or boolean)

The rule object

{
  "id": "2c3d…",
  "tenant_id": "a0b1…",
  "name": "Enterprise urgent → Ravi",
  "position": 0,
  "conditions": [
    { "field": "priority", "operator": "eq", "value": "urgent" },
    { "field": "contact_email_domain", "operator": "in", "value": ["acme.com", "globex.com"] }
  ],
  "assign_to_user_id": "b1f2…",
  "assign_strategy": "direct",
  "set_priority": "urgent",
  "add_tags": ["enterprise"],
  "is_active": true,
  "created_at": "2026-08-05T09:00:00Z",
  "updated_at": "2026-08-05T09:00:00Z"
}

All conditions on a rule are ANDed. Rules are evaluated in position order and the first match wins — position is the policy, so the list is never re-sorted for you.

GET /api/v1/support/ops/routing-rules

Ordered by position. Takes is_active, limit (1..500, default 100) and offset (0..100000).

POST /api/v1/support/ops/routing-rules

FieldTypeRequiredConstraints
namestringYes1–255 characters, unique per tenant
conditionsobject[]NoDefault []. At most 20 per rule
positionintegerNo0 <= position <= 100000, default 0
assign_to_user_idUUIDNoMust be a user in your tenant
assign_strategystringNodirect (default) or round_robin
set_prioritystringNoAt most 16 characters, lowercase identifier
add_tagsstring[]NoAt most 20, each at most 64 characters. De-duplicated
is_activebooleanNoDefault true

A rule with an empty conditions array matches everything — use it deliberately as a final catch-all at the highest position.

PATCH /api/v1/support/ops/routing-rules/reorder

Rewrites the whole evaluation order.

FieldTypeRequiredConstraints
rule_idsUUID[]Yes1–500 entries. Must be every routing rule in your tenant, exactly once

New position is the array index. A partial list returns 422 rule_ids must list every routing rule in this tenant exactly once — reordering is all-or-nothing, so two concurrent partial reorders cannot interleave into a nonsense order.

POST /api/v1/support/ops/routing-rules/evaluate

A dry run. Nothing is written; you get back what would happen.

FieldTypeConstraints
channelstringAt most 64 characters
prioritystringAt most 32 characters
statusstringAt most 32 characters
subjectstringAt most 1,000 characters
contact_emailstringAt most 320 characters
tagsstring[]At most 100 entries

Every field is optional. An absent fact fails any condition that asks about it — so send the whole picture when testing.

curl -X POST https://api.callmissed.com/api/v1/support/ops/routing-rules/evaluate \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "priority": "urgent",
    "subject": "Cannot check out",
    "contact_email": "asha@acme.com",
    "tags": ["vip"]
  }'
{
  "matched": true,
  "rule_id": "2c3d…",
  "rule_name": "Enterprise urgent → Ravi",
  "assign_to_user_id": "b1f2…",
  "assign_strategy": "direct",
  "set_priority": "urgent",
  "add_tags": ["enterprise"]
}

Only is_active: true rules are considered. When nothing matches you get {"matched": false, …, "add_tags": []} — a miss, not an error.

PATCH / DELETE /api/v1/support/ops/routing-rules/{rule_id}

Same fields as create, all optional. DELETE returns 204.


Errors

StatusWhen
403Key is missing support_ops:read / support_ops:write
404Macro, tag, rule or assignee not in your tenant
409Duplicate macro, tag or rule name
422Over 20 conditions, an unknown condition field or operator, a bad colour, an unknown key in actions, an incomplete reorder list

Nothing on this page consumes credits.