Drafts & Publishing
Conversational Agents are in Beta and available on Professional, Scale and Enterprise.
Every agent has a live configuration (what contacts talk to) and at most one draft. Changes in the agent editor go to the draft. Contacts keep talking to the live configuration until you publish.
Base URL: https://api.sendseven.com/api/v1/automation
| Method | Path | Scope | Success |
|---|---|---|---|
GET | /agents/{agent_id}/draft | automation:read | 200 draft |
PUT | /agents/{agent_id}/draft | automation:update | 200 draft |
DELETE | /agents/{agent_id}/draft | automation:update | 200 draft (now equal to live) |
POST | /agents/{agent_id}/publish | automation:update | 200 agent |
POST | /agents/{agent_id}/avatar | automation:update | 200 draft |
GET | /agents/insights/overview?days=30 | automation:read | 200 — see Insights overview |
PATCH /agents/{agent_id}, PUT /agents/{agent_id}/skills and the knowledge source endpoints still change the live agent immediately, exactly as before. Use them for automation; use the draft endpoints when you want a review step. A pending draft never overwrites those live changes when it is published (see Merge rules).
Publish state
Every agent response carries:
| Field | Notes |
|---|---|
publish_state | published or draft. draft means the agent has never been published, so it does not answer contacts yet. Agents created before drafts existed are published. |
has_unpublished_changes | true while a draft with changes exists. |
draft_updated_at | When the draft was last saved, or null. |
published_at | When the agent was last published, or null. |
POST /agents publishes immediately by default. Send "publish": false to create a never-published agent that you finish and publish later:
{"name": "Shop Support", "instructions": "Answer questions about orders.", "publish": false}
is_active and the bot priority are not part of the draft: they always apply instantly.
The draft document
GET /agents/{agent_id}/draft returns:
{
"agent_id": "…",
"bot_id": "…",
"document": {"name": "Shop Support", "instructions": "…", "skills": [], "knowledge_sources": [], "activation_rules": [], "…": "…"},
"live_document": {"…": "…"},
"publish_state": "published",
"has_unpublished_changes": true,
"draft_updated_at": "2026-10-06T09:00:00Z",
"draft_updated_by_name": "Alex Example",
"published_at": "2026-10-01T12:00:00Z",
"published_by_name": "Alex Example",
"changes": [{"section": "instructions", "fields": ["instructions"]}],
"validation": {"errors": [], "warnings": []},
"avatar_display_url": "https://…",
"widget_connections": [{"widget_id": "…", "name": "Website chat", "widget_type": "live_chat"}]
}
| Field | Notes |
|---|---|
document | The full editable configuration: the draft when one exists, otherwise the live configuration. It accepts the same fields as Create an agent plus name, avatar_url, the greeting button fields, max_credits_per_conversation, skills, knowledge_sources and activation_rules (channels and schedule). |
live_document | The live configuration, only while there are changes (for a side-by-side view). Otherwise null. |
changes | Changed fields grouped by editor section: identity, instructions, skills, knowledge, capabilities, handoff, channels, settings. |
validation.errors | What would block publishing right now (same codes as Publish blockers). |
avatar_display_url | A short-lived URL to show the draft's avatar. Do not store it. |
widget_connections | Active widgets that use this agent directly. Informational. |
Without a draft, GET returns the live configuration with has_unpublished_changes: false. It never creates a draft.
Save the draft
PUT /agents/{agent_id}/draft with {"document": {…}}.
- Send only what you change. Fields you leave out keep their draft (or live) value. Lists (
skills,knowledge_sources,activation_rules) are replaced as a whole when sent. A skill link withoutsourceskeeps its existing per-skill source override. - New list items get a server id. Omit
id(or send any unknown value) on a new knowledge source or activation rule. The response contains the id the server assigned; publishing creates the item with that id. - No change, no draft. If the saved draft ends up identical to the live configuration, the draft is removed and
has_unpublished_changesisfalse. Sending back thedocumentfromGETunchanged is always a no-op. - Unknown fields are rejected (
422). The same checks as the live endpoints apply (skills, tags, KB folders, live source URLs and limits). A blanknameis allowed in a draft but blocks publishing. avatar_urlcannot be set to a new value here (422 invalid_avatar_url); use the avatar upload. Sending the current value ornull(remove) is fine.- A draft is limited to 1 MB (
422 draft_too_large).
DELETE /agents/{agent_id}/draft throws the draft away and returns the live configuration.
Test the draft
POST /agents/{agent_id}/test accepts "config": "draft" to run the turn on the draft instead of the live configuration (default "live"). The response echoes which one ran in config. Without a draft the live configuration is used and config is live. See Test console.
Publish
POST /agents/{agent_id}/publish makes the draft live in one step and returns the updated agent. If there is no draft, it only marks a never-published agent as published.
Merge rules
The draft remembers the live configuration it started from. On save and on publish, SendSeven combines three versions: that starting point, your draft and the current live configuration.
- A field you changed in the draft wins.
- A field you did not change keeps its current live value, even if someone changed it through the API after the draft was started.
- Settings objects (
tools_config,handoff_config,language_settings, …) are combined key by key. - Lists are combined item by item (skills by
skill_id, sources and rules byid): items you added or removed in the draft are added or removed; items added live in the meantime are kept.
Publish blockers
If the draft cannot go live, nothing is changed and the response is:
{
"detail": {
"code": "publish_blocked",
"message": "The agent cannot be published yet",
"errors": [
{"section": "identity", "field": "ai_disclosure_message", "code": "ai_disclosure_missing"}
]
}
}
code | Meaning |
|---|---|
name_missing | The agent has no name. |
ai_disclosure_missing | The AI notice is set but has no text (only placeholders or spaces). Leave it empty to use the built-in notice. |
live_source_invalid | A live source is incomplete or its URL is not allowed. |
skill_missing | A linked skill was deleted or archived. |
goal_invalid | A linked skill's goal is invalid. |
handoff_schedule_empty | Hand-off hours are custom with the schedule switched on but no time ranges (section handoff). See Team availability. |
Avatar
POST /agents/{agent_id}/avatar — multipart/form-data with one file. The image goes into the draft; it is shown to contacts after you publish.
- PNG, JPEG or WebP only, max 2 MB. The file content must match its type; SVG is not accepted.
- Errors:
422 {"code": "invalid_avatar", "message": "…"}.
Returns the draft (document.avatar_url and avatar_display_url set).
Insights overview
GET /agents/insights/overview?days=30 — workspace-wide numbers for all agents, or one agent with &agent_id=AGENT_ID. days is 7, 30 (default) or 90; anything else returns 422 invalid_days. Test-console turns are not counted.
{
"range_days": 30,
"goals": [
{"skill_id": "…", "skill_title": "Book a demo", "goal_label": "Collect name, company and preferred date", "started": 42, "completed": 30, "completion_pct": 71.4}
],
"skills_by_usage": [
{"skill_id": "…", "title": "Order returns", "turns": 310, "agents": 2}
],
"kb_gaps": {"pending_count": 5},
"truncated": false
}
| Field | Notes |
|---|---|
goals | Per skill with a goal: goal runs started and completed in the window. completion_pct is null when nothing started. |
skills_by_usage | Replies that used the skill and how many agents used it. |
kb_gaps.pending_count | Knowledge-gap FAQ drafts waiting for review. |
truncated | true when the window had more than 20,000 replies; only the newest were counted. |
Per-agent conversation, hand-off and credit totals are on /hub/stats and /agents/{agent_id}/analytics.