An agent is an AI assistant built from a flow of steps. Each agent holds one flow in two forms: a draft that people and the Flow Assistant edit freely, and a published version that actually answers customers. Publishing copies a flow into the published version; the draft is a separate, versioned document.
The API calls agents bots, so the paths are /bots. To put an agent in front of customers, bind it to a channel. To start one from outside — a webhook or a schedule — see triggers.
Endpoints
GET/botsList agentsGET/bots/{botId}Retrieve an agentPOST/botsCreate an agentPUT/bots/{botId}Update an agentDELETE/bots/{botId}Delete an agentPOST/bots/{botId}/publishPublish an agentGET/bots/{botId}/draftRetrieve the draftPUT/bots/{botId}/draftSave the draftThe agent object
The object holds the published flow but only the metadata of the draft.
Attributes
idintegerUnique identifier of the agent. The API calls it a bot.orgIdintegerYour organisation.namestringAgent name, up to 255 characters.statusstringThe product usesACTIVEandPAUSED. Other values written by other clients are returned as they are. Only anACTIVEagent answers on a line.defaultLanguagestringLanguage code the agent answers in by default, e.g.aroren.publishedInstancestringnullableThe last published flow document as a serialised JSON string, ornullif the agent was never published. This is what runs for customers.draftVersionintegerVersion of the saved draft;0means no draft was ever saved; a non-zero version doesn’t guarantee a draft exists (publish can clear it). The draft itself is read with Retrieve the draft.draftUpdatedAtstringnullableWhen the draft was last saved.draftSourcestringnullableWho last saved the draft: the flow editor or the Flow Assistant.One of:editorassistantcreatedAtstringWhen the agent was created (ISO 8601).updatedAtstringWhen it was last changed.
{
"id": 55,
"orgId": 12,
"name": "Support assistant",
"status": "ACTIVE",
"publishedInstance": "<serialised flow document>",
"defaultLanguage": "ar",
"draftVersion": 14,
"draftUpdatedAt": "2026-11-05T16:20:41Z",
"draftSource": "editor",
"createdAt": "2026-09-02T07:30:00Z",
"updatedAt": "2026-11-05T16:20:41Z"
}
List agents
Returns agents newest first as a plain array. Unlike the timeline endpoints this list pages with limit and beforeBotId: pass the smallest id of the page you have as beforeBotId to fetch the next, older page, and stop when a page has fewer than limit agents.
builder.bots:readQuery parameters
limitintegerAgents per page. Default10.beforeBotIdintegerReturn agents with anidbelow this one. Omit for the newest page.
Returns
200The agent object.
curl -X GET "https://api.cx.na6iq.com/api/v1/bots" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.get(
"https://api.cx.na6iq.com/api/v1/bots",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()
data = res.json()
[
{
"id": 55,
"orgId": 12,
"name": "Support assistant",
"status": "ACTIVE",
"publishedInstance": "<serialised flow document>",
"defaultLanguage": "ar",
"draftVersion": 14,
"draftUpdatedAt": "2026-11-05T16:20:41Z",
"draftSource": "editor",
"createdAt": "2026-09-02T07:30:00Z",
"updatedAt": "2026-11-05T16:20:41Z"
}
]
Retrieve an agent
Returns one agent, including its published flow.
builder.bots:readPath parameters
botIdintegerrequiredThe agent’sid.
Returns
200The agent object.
Errors
- 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X GET "https://api.cx.na6iq.com/api/v1/bots/42" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.get(
"https://api.cx.na6iq.com/api/v1/bots/42",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()
data = res.json()
{
"id": 55,
"orgId": 12,
"name": "Support assistant",
"status": "ACTIVE",
"publishedInstance": "<serialised flow document>",
"defaultLanguage": "ar",
"draftVersion": 14,
"draftUpdatedAt": "2026-11-05T16:20:41Z",
"draftSource": "editor",
"createdAt": "2026-09-02T07:30:00Z",
"updatedAt": "2026-11-05T16:20:41Z"
}
Create an agent
Creates an empty agent with no draft and nothing published. Build its flow in the editor, or save one with Save the draft.
builder.bots:writeRequest body
namestringrequiredAgent name, up to 255 characters.defaultLanguagestringrequiredLanguage code, up to 20 characters, e.g.ar.statusstringOptional on create (defaults toACTIVE); required on update.
Returns
201The agent object.
Errors
- 400
VALIDATION_FAILED`name` or `defaultLanguage` is missing or too long — see `error.details`.
curl -X POST "https://api.cx.na6iq.com/api/v1/bots" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Support assistant",
"defaultLanguage": "ar"
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Support assistant",
"defaultLanguage": "ar"
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.post(
"https://api.cx.na6iq.com/api/v1/bots",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"name": "Support assistant",
"defaultLanguage": "ar"
},
)
res.raise_for_status()
data = res.json()
{
"id": 55,
"orgId": 12,
"name": "Support assistant",
"status": "ACTIVE",
"publishedInstance": null,
"defaultLanguage": "ar",
"draftVersion": 0,
"draftUpdatedAt": null,
"draftSource": null,
"createdAt": "2026-09-02T07:30:00Z",
"updatedAt": "2026-11-05T16:20:41Z"
}
Update an agent
Changes the agent’s name, default language or status. Set status to PAUSED to take an agent out of service without deleting it. This does not touch the flow.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.
Request body
namestringrequiredAgent name, up to 255 characters.defaultLanguagestringrequiredLanguage code, up to 20 characters, e.g.ar.statusstringOptional on create (defaults toACTIVE); required on update.
Returns
200The agent object.
Errors
- 400
VALIDATION_FAILEDA required field is missing — `status` is required on update. - 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X PUT "https://api.cx.na6iq.com/api/v1/bots/42" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Support assistant",
"defaultLanguage": "ar",
"status": "PAUSED"
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Support assistant",
"defaultLanguage": "ar",
"status": "PAUSED"
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.put(
"https://api.cx.na6iq.com/api/v1/bots/42",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"name": "Support assistant",
"defaultLanguage": "ar",
"status": "PAUSED"
},
)
res.raise_for_status()
data = res.json()
{
"id": 55,
"orgId": 12,
"name": "Support assistant",
"status": "PAUSED",
"publishedInstance": "<serialised flow document>",
"defaultLanguage": "ar",
"draftVersion": 14,
"draftUpdatedAt": "2026-11-05T16:20:41Z",
"draftSource": "editor",
"createdAt": "2026-09-02T07:30:00Z",
"updatedAt": "2026-11-05T16:20:41Z"
}
Delete an agent
Deletes the agent. Detach it from its channels first, otherwise customers on those lines are left with nobody answering.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.
Returns
204The agent is deleted.
Errors
- 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X DELETE "https://api.cx.na6iq.com/api/v1/bots/42" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
import os, requests
res = requests.delete(
"https://api.cx.na6iq.com/api/v1/bots/42",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()
Publish an agent
Makes a flow the one customers get. It overwrites the published version — there is no rollback. If the saved draft is identical to the published flow, the draft is cleared. flowJson is the whole flow document as a string, not an object: serialise it yourself. The response is the agent with its new publishedInstance.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.
Request body
flowJsonstringrequiredThe complete flow document, serialised to a JSON string. It is the same document Retrieve the draft returns.
Returns
200The agent object.
Errors
- 400
INVALID_REQUESTThe flow document is malformed, for example an edge points at a node that does not exist. - 400
VALIDATION_FAILEDThe flow failed publish checks, or `flowJson` is empty. `error.details` lists the problems. - 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X POST "https://api.cx.na6iq.com/api/v1/bots/42/publish" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"flowJson": "<serialised flow document>"
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/publish', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"flowJson": "<serialised flow document>"
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.post(
"https://api.cx.na6iq.com/api/v1/bots/42/publish",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"flowJson": "<serialised flow document>"
},
)
res.raise_for_status()
data = res.json()
{
"id": 55,
"orgId": 12,
"name": "Support assistant",
"status": "ACTIVE",
"publishedInstance": "<serialised flow document>",
"defaultLanguage": "ar",
"draftVersion": 14,
"draftUpdatedAt": "2026-11-05T16:20:41Z",
"draftSource": "editor",
"createdAt": "2026-09-02T07:30:00Z",
"updatedAt": "2026-11-05T16:20:41Z"
}
Retrieve the draft
Returns the saved work-in-progress flow. An agent that has never had a draft answers 404 BOT_DRAFT_NOT_FOUND, which is a normal state, not a fault.
builder.bots:readPath parameters
botIdintegerrequiredThe agent’sid.
Returns
200The agent object.
Errors
- 404
BOT_DRAFT_NOT_FOUNDThere is no draft to return. The same code answers a missing agent, another organisation’s agent, or a draft cleared by publish.
curl -X GET "https://api.cx.na6iq.com/api/v1/bots/42/draft" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/draft', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.get(
"https://api.cx.na6iq.com/api/v1/bots/42/draft",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()
data = res.json()
{
"botId": 55,
"flowJson": "<serialised flow document>",
"version": 14,
"updatedAt": "2026-11-05T16:20:41Z",
"source": "editor"
}
Save the draft
Saves the work-in-progress flow. Only its structure is checked, so a flow that would fail publishing still saves. Pass baseVersion — the version you last read — to make the save a compare-and-set: if someone else (another tab, or the Flow Assistant) saved since, you get a 409 instead of overwriting their work. Without baseVersion the save is unconditional.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.
Request body
flowJsonstringrequiredThe flow document, serialised to a JSON string.baseVersionintegerThe draftversionyour edit started from.
Returns
200The agent object.
Errors
- 400
INVALID_REQUESTThe flow document is malformed, for example an edge points at a node that does not exist. - 400
VALIDATION_FAILED`flowJson` is blank, or `baseVersion` is negative. - 409
BOT_DRAFT_VERSION_CONFLICTThe draft moved on since `baseVersion`. `error.data` has `currentVersion`, `updatedAt` and `source` so you can offer to load the newer draft. - 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X PUT "https://api.cx.na6iq.com/api/v1/bots/42/draft" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"flowJson": "<serialised flow document>",
"baseVersion": 14
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/draft', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"flowJson": "<serialised flow document>",
"baseVersion": 14
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.put(
"https://api.cx.na6iq.com/api/v1/bots/42/draft",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"flowJson": "<serialised flow document>",
"baseVersion": 14
},
)
res.raise_for_status()
data = res.json()
{
"botId": 55,
"flowJson": "<serialised flow document>",
"version": 15,
"updatedAt": "2026-11-07T08:05:12Z",
"source": "editor"
}