curl --request POST \
--url https://api.neoagent.io/public-api/channels \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"internal_name": "<string>",
"agent_id": 123,
"color_icon_url": "<string>",
"description": "<string>",
"display_name": "<string>",
"idle_reset_minutes": 123,
"outline_icon_url": "<string>",
"relay_prefix": "<string>",
"relay_show_ticket_status": true,
"relay_show_ticket_title": true,
"show_agent_progress_steps": true,
"status": "ACTIVE",
"welcome_message": "<string>"
}
'import requests
url = "https://api.neoagent.io/public-api/channels"
payload = {
"internal_name": "<string>",
"agent_id": 123,
"color_icon_url": "<string>",
"description": "<string>",
"display_name": "<string>",
"idle_reset_minutes": 123,
"outline_icon_url": "<string>",
"relay_prefix": "<string>",
"relay_show_ticket_status": True,
"relay_show_ticket_title": True,
"show_agent_progress_steps": True,
"status": "ACTIVE",
"welcome_message": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
internal_name: '<string>',
agent_id: 123,
color_icon_url: '<string>',
description: '<string>',
display_name: '<string>',
idle_reset_minutes: 123,
outline_icon_url: '<string>',
relay_prefix: '<string>',
relay_show_ticket_status: true,
relay_show_ticket_title: true,
show_agent_progress_steps: true,
status: 'ACTIVE',
welcome_message: '<string>'
})
};
fetch('https://api.neoagent.io/public-api/channels', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.neoagent.io/public-api/channels",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'internal_name' => '<string>',
'agent_id' => 123,
'color_icon_url' => '<string>',
'description' => '<string>',
'display_name' => '<string>',
'idle_reset_minutes' => 123,
'outline_icon_url' => '<string>',
'relay_prefix' => '<string>',
'relay_show_ticket_status' => true,
'relay_show_ticket_title' => true,
'show_agent_progress_steps' => true,
'status' => 'ACTIVE',
'welcome_message' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.neoagent.io/public-api/channels"
payload := strings.NewReader("{\n \"internal_name\": \"<string>\",\n \"agent_id\": 123,\n \"color_icon_url\": \"<string>\",\n \"description\": \"<string>\",\n \"display_name\": \"<string>\",\n \"idle_reset_minutes\": 123,\n \"outline_icon_url\": \"<string>\",\n \"relay_prefix\": \"<string>\",\n \"relay_show_ticket_status\": true,\n \"relay_show_ticket_title\": true,\n \"show_agent_progress_steps\": true,\n \"status\": \"ACTIVE\",\n \"welcome_message\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.neoagent.io/public-api/channels")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"internal_name\": \"<string>\",\n \"agent_id\": 123,\n \"color_icon_url\": \"<string>\",\n \"description\": \"<string>\",\n \"display_name\": \"<string>\",\n \"idle_reset_minutes\": 123,\n \"outline_icon_url\": \"<string>\",\n \"relay_prefix\": \"<string>\",\n \"relay_show_ticket_status\": true,\n \"relay_show_ticket_title\": true,\n \"show_agent_progress_steps\": true,\n \"status\": \"ACTIVE\",\n \"welcome_message\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.neoagent.io/public-api/channels")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"internal_name\": \"<string>\",\n \"agent_id\": 123,\n \"color_icon_url\": \"<string>\",\n \"description\": \"<string>\",\n \"display_name\": \"<string>\",\n \"idle_reset_minutes\": 123,\n \"outline_icon_url\": \"<string>\",\n \"relay_prefix\": \"<string>\",\n \"relay_show_ticket_status\": true,\n \"relay_show_ticket_title\": true,\n \"show_agent_progress_steps\": true,\n \"status\": \"ACTIVE\",\n \"welcome_message\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"agent_id": 123,
"audience": "MSP",
"color_icon_url": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"description": "<string>",
"display_name": "<string>",
"id": "<string>",
"idle_reset_minutes": 123,
"install_next_step": "<string>",
"install_required": true,
"internal_name": "<string>",
"outline_icon_url": "<string>",
"relay_identity": "TECHNICIAN",
"relay_prefix": "<string>",
"relay_show_ticket_status": true,
"relay_show_ticket_title": true,
"show_agent_progress_steps": true,
"show_tickets_tab": true,
"status": "ACTIVE",
"type": "TEAMS",
"updated_at": "2023-11-07T05:31:56Z",
"welcome_message": "<string>"
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timings_ms": {},
"pagination": {
"has_more": true,
"next_cursor": "<string>"
},
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}Create a channel
Bind a CONVERSATIONAL agent to a transport. An ACTIVE channel must have an agent. audience (MSP | END_USER) declares the surface’s trust domain and must match the bound agent’s audience — an END_USER channel may only bind a user-facing agent, and an MSP (internal) channel only an internal agent. audience is fixed for the channel’s whole life; pick it carefully. internal_name must be unique among your live channels (compared trimmed and case-insensitively). Omitting show_agent_progress_steps takes the audience default — the live “working…” card is off on an END_USER channel and on for MSP; send it explicitly to override, and it stays editable afterwards. idle_reset_minutes sets how long a Teams conversation may sit idle before the bot offers the user a “Start fresh / Continue” card; omit it to keep only the built-in behaviour, which offers the same card on conversations that have grown large enough to be slow to resume.
A created channel is not yet reachable. The response comes back with install_required TRUE and the outstanding step in install_next_step. A Teams channel’s app still has to be installed into a Microsoft 365 tenant, which an administrator of that tenant approves — sending that administrator an install link does not clear it either. A Slack channel’s app is installed into each client’s Slack workspace by a Slack admin of that workspace, from the channel’s Slack install link. Do not report the channel as live while install_required is TRUE. type=SLACK takes only an END_USER channel and is refused with validation_error otherwise. An MSP may hold several MSP-audience (internal) Teams channels: each is stamped at creation with its own bot identity from a fixed pool, so they install into the MSP’s own tenant side by side. Creating one is refused with validation_error when the workspace has no Microsoft 365 directory connected yet, or once the pool is used up; the message names the remedy. End-user channels are not capped — each installs into a different end client’s tenant.
curl --request POST \
--url https://api.neoagent.io/public-api/channels \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"internal_name": "<string>",
"agent_id": 123,
"color_icon_url": "<string>",
"description": "<string>",
"display_name": "<string>",
"idle_reset_minutes": 123,
"outline_icon_url": "<string>",
"relay_prefix": "<string>",
"relay_show_ticket_status": true,
"relay_show_ticket_title": true,
"show_agent_progress_steps": true,
"status": "ACTIVE",
"welcome_message": "<string>"
}
'import requests
url = "https://api.neoagent.io/public-api/channels"
payload = {
"internal_name": "<string>",
"agent_id": 123,
"color_icon_url": "<string>",
"description": "<string>",
"display_name": "<string>",
"idle_reset_minutes": 123,
"outline_icon_url": "<string>",
"relay_prefix": "<string>",
"relay_show_ticket_status": True,
"relay_show_ticket_title": True,
"show_agent_progress_steps": True,
"status": "ACTIVE",
"welcome_message": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
internal_name: '<string>',
agent_id: 123,
color_icon_url: '<string>',
description: '<string>',
display_name: '<string>',
idle_reset_minutes: 123,
outline_icon_url: '<string>',
relay_prefix: '<string>',
relay_show_ticket_status: true,
relay_show_ticket_title: true,
show_agent_progress_steps: true,
status: 'ACTIVE',
welcome_message: '<string>'
})
};
fetch('https://api.neoagent.io/public-api/channels', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.neoagent.io/public-api/channels",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'internal_name' => '<string>',
'agent_id' => 123,
'color_icon_url' => '<string>',
'description' => '<string>',
'display_name' => '<string>',
'idle_reset_minutes' => 123,
'outline_icon_url' => '<string>',
'relay_prefix' => '<string>',
'relay_show_ticket_status' => true,
'relay_show_ticket_title' => true,
'show_agent_progress_steps' => true,
'status' => 'ACTIVE',
'welcome_message' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.neoagent.io/public-api/channels"
payload := strings.NewReader("{\n \"internal_name\": \"<string>\",\n \"agent_id\": 123,\n \"color_icon_url\": \"<string>\",\n \"description\": \"<string>\",\n \"display_name\": \"<string>\",\n \"idle_reset_minutes\": 123,\n \"outline_icon_url\": \"<string>\",\n \"relay_prefix\": \"<string>\",\n \"relay_show_ticket_status\": true,\n \"relay_show_ticket_title\": true,\n \"show_agent_progress_steps\": true,\n \"status\": \"ACTIVE\",\n \"welcome_message\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.neoagent.io/public-api/channels")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"internal_name\": \"<string>\",\n \"agent_id\": 123,\n \"color_icon_url\": \"<string>\",\n \"description\": \"<string>\",\n \"display_name\": \"<string>\",\n \"idle_reset_minutes\": 123,\n \"outline_icon_url\": \"<string>\",\n \"relay_prefix\": \"<string>\",\n \"relay_show_ticket_status\": true,\n \"relay_show_ticket_title\": true,\n \"show_agent_progress_steps\": true,\n \"status\": \"ACTIVE\",\n \"welcome_message\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.neoagent.io/public-api/channels")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"internal_name\": \"<string>\",\n \"agent_id\": 123,\n \"color_icon_url\": \"<string>\",\n \"description\": \"<string>\",\n \"display_name\": \"<string>\",\n \"idle_reset_minutes\": 123,\n \"outline_icon_url\": \"<string>\",\n \"relay_prefix\": \"<string>\",\n \"relay_show_ticket_status\": true,\n \"relay_show_ticket_title\": true,\n \"show_agent_progress_steps\": true,\n \"status\": \"ACTIVE\",\n \"welcome_message\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"agent_id": 123,
"audience": "MSP",
"color_icon_url": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"description": "<string>",
"display_name": "<string>",
"id": "<string>",
"idle_reset_minutes": 123,
"install_next_step": "<string>",
"install_required": true,
"internal_name": "<string>",
"outline_icon_url": "<string>",
"relay_identity": "TECHNICIAN",
"relay_prefix": "<string>",
"relay_show_ticket_status": true,
"relay_show_ticket_title": true,
"show_agent_progress_steps": true,
"show_tickets_tab": true,
"status": "ACTIVE",
"type": "TEAMS",
"updated_at": "2023-11-07T05:31:56Z",
"welcome_message": "<string>"
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timings_ms": {},
"pagination": {
"has_more": true,
"next_cursor": "<string>"
},
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}Authorizations
Authorization: Bearer <token> where <token> is either a neo_sk_<env>_<secret> API key (service account) or a Microsoft Entra ID access token (dashboard user).
Body
Trust domain a tool / agent / channel may be exposed to.
MSP — technician/dashboard surfaces; tools may take MSP-wide scope and accept company/
contact identifiers as LLM-supplied parameters. An agent/channel with this audience is the
MSP's own INTERNAL surface (the dashboard labels MSP as "Internal").
END_USER — the MSP's end-client users (white-label Teams bots, embedded end-company chat).
END_USER tools must derive ALL identity (end-company, contact) from the session context,
never from tool parameters, so a prompt-injected or curious end user cannot widen scope.
The two surfaces are disjoint by construction: resolve_end_user_toolbox resolves only
END_USER tools (deny-by-default, no auto-injection), and resolve_full_toolbox is for
MSP surfaces only.
MSP, END_USER Transport a channel binds an agent to. TEAMS and SLACK are active (SLACK is END_USER only); PORTAL and EMAIL are reserved.
TEAMS, SLACK, PORTAL, EMAIL Who a technician's PSA note is attributed to when it is relayed into an end user's chat: TECHNICIAN (the note's real author, shown with their name and an avatar, falling back to the channel's relay_prefix label when the PSA supplies no author), BRAND (always the relay_prefix label and the channel's own icon, never an individual's name), or OFF (the note is relayed as plain text with no attribution). Omit it to derive the mode from relay_prefix.
TECHNICIAN, BRAND, OFF ACTIVE, DISCONNECTED 