Manage tags and workflows from your agent
The MCP authoring loop — resolve and create tags, tag threads singly or in bulk, and capture repeated treatments as reusable workflows.
A connected agent can do more than act on existing objects — it can set them up: create tags, apply them across threads, and capture a repeated manual treatment as a workflow that either runs automatically or waits to be applied on request. This guide walks the whole authoring loop. Connect a client first: Connect an AI agent to Beep (MCP).
Scopes and roles at a glance:
| Action | Scope | Role required |
|---|---|---|
| List tags / workflows | inbox:read | Any workspace member |
| Apply / remove tags, apply workflows | inbox:write | Any workspace member |
| Create tags, create / update workflows | admin:workspace | Workspace Admin+ |
Authoring is gated twice: the connection must hold the admin:workspace scope and the connected user must be a Workspace Admin (or higher) in the target workspace. A token can never author on behalf of a user whose own role couldn't.
Resolve tag names to IDs
Tag tools take tag IDs, so name resolution comes first. list_tags supports a server-side, case-insensitive substring filter so you never page the whole catalog to find one tag:
{ "workspace_id": "…", "name_contains": "hostile" }- Pagination is keyset-based: each page carries a
next_cursor, which isnullwhen the list is exhausted (default page size 100, max 500). A capped page is always announced — checknext_cursorbefore concluding a tag doesn't exist. scopeselects the namespace:general(conversation/thread tags — the default),campaign(campaign tags), orall. These are separate namespaces; the same name can exist in both.is_selectable: falsemarks single-select group parents. A group parent is a container, not a taggable value — never pass a parent's ID toapply_tags,remove_tags, orbulk_tag_threads. Apply one of its child values instead (children carry the parent's ID inparent_tag_id).
Create tags
create_tag (Workspace Admin+, admin:workspace) creates all three kinds of tags. The shapes are mutually exclusive:
1. A flat tag — the default. Name plus optional color (hex, e.g. #3b82f6) and description:
{
"workspace_id": "…",
"name": "hostile",
"color": "#dc2626",
"idempotency_key": "create-hostile-tag-1"
}2. A single-select group — a parent with 1–20 ordered values, created atomically in one call. Array order becomes the display order. Only one value of a group can be on a thread at a time:
{
"workspace_id": "…",
"name": "Sentiment",
"tag_type": "single_select",
"values": [
{ "name": "Positive" },
{ "name": "Neutral" },
{ "name": "Negative" }
],
"idempotency_key": "create-sentiment-group-1"
}3. A new value in an existing group — pass parent_tag_id only (no tag_type, no values). The parent must be an existing single-select group in the same workspace.
Notes:
- Groups are conversation-scope only.
scope: "campaign"is valid for flat tags; single-select groups and group values must begeneralscope. - Duplicate names don't dead-end. If the tag already exists, the validation error includes the existing tag's ID, so the agent can proceed straight to applying it without a second
list_tagscall.
Tag a thread
apply_tags and remove_tags take a thread_id, 1–10 tag_ids, and an idempotency_key.
Validation runs first, as a whole set. Every tag in the call is checked before anything is applied — a wrong-scope, deleted, cross-workspace, or unknown tag, a group parent, or two values of the same single-select group fails the entire request with per-tag reasons. There is no partial application, so a success response never hides a rejected tag.
Single-select exclusivity is enforced on apply. Applying one value of a group replaces whatever sibling value was on the thread — you never need to remove Neutral before applying Negative. That's also why applying two values of one group in a single call is rejected: the final state would be ambiguous.
Removal is more permissive. Removing multiple values of the same group in one call is allowed — it's the repair path if a thread ever ends up with more than one sibling.
Both directions are quietly idempotent at the tag level: applying an already-present tag or removing an absent one reports no_op for that tag rather than failing.
{
"thread_id": "…",
"tag_ids": ["<negative-tag-id>"],
"idempotency_key": "tag-thread-42-1"
}Response:
{ "thread_id": "…", "results": [{ "tag_id": "…", "status": "applied" }] }Bulk tagging
bulk_tag_threads applies or removes 1–10 tags across up to 100 threads in one call:
{
"workspace_id": "…",
"action": "apply",
"tag_ids": ["<negative-tag-id>"],
"thread_ids": ["…", "…"],
"search_preview_id": "…",
"idempotency_key": "bulk-tag-negative-1"
}- Tag validation happens before any thread is touched — the same whole-set rules as
apply_tags, including single-select sibling replacement on each thread. thread_idsis always explicit. Passingsearch_preview_id(minted bysearch_messageswithcreate_preview: true) constrains the call to threads that were in that search's snapshot — the guardrail for "tag what we just found."- Results are per-thread:
succeeded,skipped(already in the desired state), orfailed— one thread failing never aborts the rest.
Create workflows
A workflow is an ordered list of steps applied to conversation threads. create_workflow (Workspace Admin+, admin:workspace) is the authoring tool; the central choice is the trigger:
manual— a reusable playbook. It never runs on its own; it's applied on request viaapply_workfloworbulk_apply_workflow. Use this for "a one-off I might want again."- Any other trigger type — runs automatically when its event fires.
| Trigger type | Fires when… | trigger_config |
|---|---|---|
manual | Applied on request | {} |
inbound_keyword | An inbound message matches | { match_type, source, keywords, case_sensitive?, campaign_id? } (campaign_id may be a campaign ID or "any") |
tag_added | A tag is applied to a thread | { tag_id } |
record_created | A contact record is created | { source: "ui" | "api" | "import" | "inbound" | "any" } |
list_member_added | A contact joins a list | { list_id } (a list ID or "any") |
endpoint_status_change | A contact endpoint changes status | { endpoint_type, from_status, to_status } |
scheduled | On a cron schedule | { cron, timezone, target: { type: "list" | "saved_audience", id } } |
Steps are { action_type, config } pairs (1–20, executed in order):
| Action type | Config | Effect |
|---|---|---|
add_tag | { tag_id } | Apply a conversation tag |
draft_response | { response_id } | Insert a saved response as a suggested draft |
send_response | { response_id } | Send a saved response without review |
assign_self | {} | Assign the thread to the applying user |
unassign | {} | Clear the assignment |
close / reopen | {} | Close or reopen the thread |
mark_read / mark_unread | {} | Set the applying user's read state |
Two authoring rules to know up front:
- Actor-dependent steps require a
manualtrigger.assign_self,mark_read,mark_unread, anddraft_responseall act as someone — and an automated run has no one to act as. Combining any of them with a non-manual trigger is a validation error. - Keyword autoresponders are strict. An
inbound_keywordworkflow that contains asend_responsestep must use exact, case-insensitive keyword matching with a single send step. Looser matching (contains, word lists) is fine for non-sending keyword workflows.
Every ID referenced in a trigger or step config (tag_id, response_id, list_id, …) is validated against the workspace at authoring time — an invented or cross-workspace ID is rejected, not stored.
{
"workspace_id": "…",
"name": "Negative reply triage",
"trigger_type": "manual",
"trigger_config": {},
"steps": [
{ "action_type": "add_tag", "config": { "tag_id": "<negative-tag-id>" } },
{ "action_type": "close", "config": {} }
],
"idempotency_key": "create-negative-triage-1"
}Tip: before authoring, call list_workflows with include_inactive: true and include_triggered: true to see everything that already exists (defaults return only active manual workflows — the apply-flow view) and avoid duplicating a name.
Update workflows
update_workflow follows the same rules as create, plus:
name,description,conditions, andis_activecan each change independently — flippingis_activealone is how you pause or resume a workflow.trigger_type,trigger_config, andstepschange together or not at all. A trigger and its steps are one unit; you can't swap a trigger while keeping stale steps.- Campaign-managed workflows are read-only here. Workflows created and owned by a campaign (autoresponders) must be edited through the campaign itself.
Bulk-apply a manual workflow
bulk_apply_workflow runs an active, manual workflow across up to 100 threads in one call — with the cap dropping to 25 when the workflow contains a send_response step, because that is bulk sending.
{
"workflow_id": "…",
"workspace_id": "…",
"thread_ids": ["…", "…"],
"search_preview_id": "…",
"idempotency_key": "apply-triage-batch-1"
}- Each thread gets its own execution audit record, exactly as if you'd applied the workflow one thread at a time.
- One thread failing never aborts the rest — results are per-thread.
- Retries are safe with the same
idempotency_key. If the call is interrupted partway, retry it with the same key: threads that already completed are skipped (their stored results are replayed), and sending steps never fire twice.
End to end: find → tag → close → capture as a workflow
The canonical loop, prompted as: "Find the hostile negative replies, tag them hostile, close them — then make that a workflow."
-
Find the threads and snapshot them.
search_messageswithcreate_preview: truereturns the matches plus asearch_preview_id.{ "workspace_id": "…", "query": "negative hostile angry", "direction": "in", "create_preview": true } -
Resolve or create the tag.
list_tagswithname_contains: "hostile"; if nothing matches,create_tag(requiresadmin:workspace+ Workspace Admin). A duplicate-name error hands back the existing ID either way. -
Tag the batch.
bulk_tag_threadswithaction: "apply", the matchedthread_ids, and thesearch_preview_id. -
Close the batch.
bulk_close_threadswith the samethread_idsandsearch_preview_id. -
Capture the treatment as a workflow.
create_workflowwith steps[add_tag, close]and one of two triggers:inbound_keyword— future matching inbound messages get tagged and closed automatically;manual— a named playbook the agent re-applies later withbulk_apply_workflowover a fresh search preview, on request.
Well-behaved agents offer step 5 unprompted: after any repeated manual treatment, suggest capturing it as a workflow and let the user pick automatic versus on-request.
Updated 2 months ago