Assistants, messaging and calls
Create assistants, tie phone numbers and email addresses to users, and reach Prime over iMessage, SMS, WhatsApp, email, Slack, Teams, phone calls and live voice.
Assistants, messaging and calls
An assistant is the identity your users talk to: a name, a personality, a writing style, an email inbox and phone lines. Users reach it on the channels they already use. AGNT works out who is writing, opens or continues that person's chat, and Prime answers in the same thread. This guide follows Harborview as it gives Jane Rivera an assistant named Ava, connects Jane's phone, adds phone lines, and talks to Ava over the API, Slack and voice.
How it works
Jane texts Ava's number (+14155550142) from +14155550187
│
▼
AGNT matches the sender ──► verified phone identifier → Jane Rivera
│ (no match → unknown-sender handling)
▼
Jane's chat with Ava on iMessage ──► Prime runs a turn with Jane's memory,
│ tasks, connections and settings
▼
Ava replies on the same channel, in the same thread
| Record | Description |
|---|---|
| Assistant | The identity: profile fields, an email address, phone lines (channels), a voice, and a scope that sets who can use it. |
| Identifier | A handle that belongs to one user: an email address, a phone number, or a sign-in account. A verified phone number or email address tells AGNT who sent an inbound message. |
| Chat | One conversation thread. Each 1:1 thread on a channel is its own chat. A user's tasks are shared across all of their chats. |
A phone thread belongs to the person, not the number. One assistant number can serve every user of your app, because AGNT tells users apart by the verified number they text from. When a number moves to a different user, the new holder does not inherit the previous holder's chat, history or tasks.
Create an assistant
Create Ava on Jane's roster. The call is idempotent on name for that user, and the first assistant on an empty roster becomes the user's primary.
curl https://api.agnt.ai/users/6702b1e8f9a0b1c2d3e4f5a6/assistants \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ava",
"email": "[email protected]",
"personality": "Warm, brief and precise. Confirms details before acting.",
"writingStyle": { "professionalism": 2, "humor": 1, "emojiUse": 0 }
}'
{
"ok": true,
"assistant": {
"id": "6702b4c1d2e3f4a5b6c7d8e9",
"account": "66f1a2b3c4d5e6f708192a3b",
"organization": null,
"team": null,
"user": "6702b1e8f9a0b1c2d3e4f5a6",
"scope": "user",
"name": "Ava",
"email": "[email protected]",
"avatarUrl": null,
"signature": "--\nAssistant to {{name}}",
"personality": "Warm, brief and precise. Confirms details before acting.",
"writingStyle": { "professionalism": 2, "humor": 1, "emojiUse": 0 },
"voice": null,
"status": "active",
"talkToStrangers": true,
"inboxPlatform": null,
"channels": {
"imessage": null,
"sms": null,
"whatsapp": null,
"voice": { "status": "none" }
},
"isPrimary": true,
"createdAt": "2026-11-02T16:24:11.000Z",
"updatedAt": "2026-11-02T16:24:11.000Z"
}
}
The response is 201 for a new assistant and 200 when Ava already exists on Jane's roster.
| Name | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The assistant's name. |
email | string | No | The assistant's own address, unique within your account. Generated when omitted. |
personality, backstory | string | No | Free text that shapes how the assistant writes and talks. |
writingStyle | object | No | professionalism, humor and emojiUse, each 0 to 3, plus optional free text characterInspiration, externalCommunication and oneOnOneCommunication. |
avatar | string | No | Image URL. Returned as avatarUrl. |
signature, signatureEditorMode | string | No | Email signature, and visual or html. |
gender, pronouns, country | string | No | Persona details. country (ISO 3166-1 alpha-2) defaults to the user's country and never changes the output language. |
voice | object | No | { voiceId, provider } for calls. See Live voice. |
metadata | object | No | Your own key-value data. |
channels is read-only. Phone lines are added as described in Phone lines. inboxPlatform reports google or outlook when the assistant runs on a real Gmail or Outlook mailbox.
To check an address first, call GET /assistants/check-email?slug=ava, which returns { available, email }. GET /assistants/generate?name=Ava suggests free addresses.
With the TypeScript SDK:
import { AgntClient } from '@agnt-sdk/client';
const client = await AgntClient.create();
const assistants = await client.assistants.list();
await client.assistants.update('6702b4c1d2e3f4a5b6c7d8e9', {
personality: 'Warm, brief and precise.',
});
Shared assistants
POST /assistants creates an assistant that several users share. Call it with an account-wide key and no X-User-Email.
| Name | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The assistant's name. |
email | string | Yes | The assistant's address, unique within your account. |
scope | string | No | account (default), organization, team or user. Sets who can see and hire the assistant. |
organization, team | string | No | The organization or team, for those scopes. |
talkToStrangers | boolean | No | Whether the assistant engages people who are neither users of your app nor known contacts. true by default. |
It also accepts every profile field in the table above.
Rosters and the primary assistant
Every assistant a user can see is on their roster, and one roster member is their primary: the assistant that answers for that user by default. A user can have several assistants, each with its own identity and lines.
curl https://api.agnt.ai/users/6702b1e8f9a0b1c2d3e4f5a6/assistants/6702b4c1d2e3f4a5b6c7d8e9/primary \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY"
| Method | Path | Description |
|---|---|---|
GET | /users/:userId/assistants | The roster, with isPrimary on each assistant and primaryAssistantId. |
POST | /users/:userId/assistants/:assistantId/hire | Add an assistant the user can see to their roster. |
POST | /users/:userId/assistants/:assistantId/primary | Make an assistant the primary. Returns the updated user. |
DELETE | /users/:userId/assistants/:assistantId | Release one of the user's own assistants. |
Releasing an assistant deletes it and retires its email address. The call returns 409 for the primary, and for an assistant with open tasks unless you pass ?transfer=primary to move that work to the primary. Shared and account-wide assistants cannot be released (403).
Identifiers
AGNT matches inbound messages to users through identifiers. A phone number or secondary email resolves a sender only when it is verified. An unverified number is stored but identifies no one, so a text from it is handled as coming from an unknown sender.
Add Jane's mobile number as a verified identifier:
curl https://api.agnt.ai/identifiers \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "phone",
"value": "+14155550187",
"userId": "6702b1e8f9a0b1c2d3e4f5a6",
"verified": true,
"label": "mobile"
}'
The response is 201 with the new identifier and the user it belongs to.
| Name | Type | Required | Description |
|---|---|---|---|
type | string | Yes | email, phone, google, microsoft, apple, external_id, employee_id or custom. |
value | string | Yes | The handle. Unique within your account. |
userId | string | No | The user to add it to, with an account-wide key. Called as a user, it is added to that user. |
verified | boolean | No | Whether the handle is verified. Honored only from account-wide credentials. |
label | string | No | Your label, such as mobile. |
A verified phone number decides whose texts land in whose chat, and a verified email decides who signs in as whom. For that reason only account-wide credentials (the console or an account-wide API key) set verified. Set it only for numbers and addresses you have verified yourself. User-scoped callers verify through the hosted app's one-time-code flow, and a verified value they send is ignored.
Phone numbers are stored in E.164 form. A number without a leading + is parsed with the user's country as the default. An unparseable number returns 400 with invalid_phone. A value already held by any user in your account returns 400.
Phone identifiers also report what AGNT has learned about the number: iMessageCapable, whatsAppCapable, preferredTextPlatform, and messagingWindow (when the person last messaged the assistant, per channel).
List Jane's phone numbers as Jane:
curl "https://api.agnt.ai/identifiers?type=phone" \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]"
const jane = client.as('[email protected]');
const phones = await jane.identifiers.list({ type: 'phone' });
| Type | Use |
|---|---|
email | Sign-in and secondary addresses. POST /identifiers/:identifierId/make-primary makes one the user's primary email. |
phone | Numbers the user texts and calls from. |
google, microsoft, apple | Sign-in and connected accounts. |
external_id, employee_id, custom | Your own keys for the user. |
POST /identifiers/:identifierId/link moves an identifier to another user and requires account-wide credentials. A user's last identifier and their primary email cannot be deleted.
Channels
| Channel | 1:1 | Groups | Setup |
|---|---|---|---|
| iMessage | Yes | Yes | Assistant phone line |
| SMS | Yes | Yes | Assistant phone line |
| Yes | No | Assistant phone line | |
| Email (AGNT-hosted inbox) | Yes | Threads with several people | Automatic, at the assistant's email |
| Email (the user's Gmail or Outlook) | Yes | Threads with several people | The user connects over OAuth |
| Slack | DMs | Channels | Each assistant is its own Slack app |
| Microsoft Teams | Yes | No | An organization or team admin connects Teams |
| Phone calls | Inbound and outbound | No | Voice line on the assistant's number |
| Live voice | In your app | No | A voice engine on the assistant |
Phone lines
Request numbers for the user's primary assistant with POST /phone-line-requests, called as the user.
curl https://api.agnt.ai/phone-line-requests \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{
"requestedChannels": { "imessage": true, "sms": true, "voice": true },
"notes": "415 area code"
}'
{
"ok": true,
"request": {
"id": "6702cc48f9a0b1c2d3e4f5a6",
"assistant": "6702b4c1d2e3f4a5b6c7d8e9",
"requestedChannels": { "imessage": true, "sms": true, "whatsapp": false, "voice": true },
"notes": "415 area code",
"status": "pending",
"createdAt": "2026-11-02T16:27:03.000Z"
}
}
| Name | Type | Required | Description |
|---|---|---|---|
requestedChannels | object | No | imessage, sms, whatsapp and voice, each a boolean that defaults to false. |
notes | string | No | Preferences for the numbers, such as an area code. |
When the numbers are ready, the request's status becomes fulfilled and the numbers appear on the assistant under channels.imessage, channels.sms and channels.whatsapp. An assistant's voice line uses the same number as its iMessage line. channels.voice.status reports the voice line: none, provisioning, pending_a2p (waiting on US carrier registration), active or failed. A dismissed request carries a dismissalReason.
GET /phone-line-requests/mine returns the user's latest request, or "request": null. POST /phone-line-requests returns 400 when the user has no assistant or already has a pending request.
iMessage, SMS and WhatsApp
Jane texts +14155550142. AGNT matches her verified number, continues her chat with Ava, and Prime replies on the same channel.
In iMessage and SMS group threads, the assistant works for its owner. Anything it does on the owner's behalf follows the owner's autonomy settings, and the owner's private conversations stay private.
WhatsApp allows a business to message a person freely only within 24 hours of that person's last message. AGNT tracks this per person in messagingWindow.
Every assistant has an AGNT-hosted inbox at its email address, and mail sent there reaches Prime like any other message. Inbound mail is dropped if it fails spam or virus checks, if DMARC fails for a domain that publishes p=reject, or if SPF and DKIM both fail. Prime treats inbound mail as untrusted content.
An assistant can run on a real Gmail or Outlook mailbox instead, which the user authorizes in the hosted app. inboxPlatform then reports google or outlook.
A user can also connect their own Gmail or Outlook over OAuth from the hosted app. Prime can then read, triage and draft in the user's mailbox and send as the user. Sending as the user is an outbound action and follows the user's autonomy settings.
Slack
Each assistant can be its own Slack app, with its own bot name and avatar. POST /assistants/:assistantId/slack-apps creates the app and returns a manifest to apply in Slack. Give Ava a Slack app in Harborview's workspace, called as Jane:
curl https://api.agnt.ai/assistants/6702b4c1d2e3f4a5b6c7d8e9/slack-apps \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{ "kind": "workspace", "botDisplayName": "Ava" }'
The response is 201 with slackApp (status is pending_credentials until the app's Slack credentials are in place) and manifest (manifestJson, createAppUrl, eventsUrl and redirectUrls). Calling again for an existing app returns it with 200.
kind | The app | Call with | Errors |
|---|---|---|---|
marketplace | Your listed app, for an account-wide assistant | An account key, no X-User-Email | 403 for any other caller. 400 not_system_assistant for an assistant that is not account-wide. |
workspace | An app internal to one Slack workspace, for a user's own or shared assistant | The user (X-User-Email), who must have the assistant and whose organization allows Slack | 403 user_required, 403 slack_not_allowed, 403 system_assistant_console_only for an account-wide assistant. |
Users install the app with "Add to Slack" (GET /oauth/slack/assistant/authorize) and link their Slack identity with "Sign in with Slack" (GET /oauth/slack/assistant/link). GET /assistants and GET /assistants/:assistantId include a per-user slack status: configured, connected, and deep links to message the bot. Each workspace install has a kill switch and a setting for how the assistant treats Slack users who are not users of your app.
Microsoft Teams
An organization or team admin connects Microsoft Teams with GET /oauth/teams/authorize?target=team&targetId=6702ab52c3d4e5f6a7b8c9d0, or target=org for the whole organization. Your account needs a Teams app configured first.
Chats
A chat is one conversation thread between a user and an assistant. Messages on a channel land in that channel's chat automatically. Use the chats API to run an in-app conversation in your own product, or to read any of a user's threads. Every chats call is user-scoped.
Start a chat and get a reply
-
Open a chat between Jane and Ava.
curl https://api.agnt.ai/chats \ -X POST \ -H "Authorization: Bearer $AGNT_API_KEY" \ -H "X-User-Email: [email protected]" \ -H "Content-Type: application/json" \ -d '{ "assistantId": "6702b4c1d2e3f4a5b6c7d8e9", "title": "Brightline renewal" }'{ "ok": true, "chat": { "id": "6702c5d1e2f3a4b5c6d7e8f9", "title": "Brightline renewal", "status": "active", "platform": "chat", "user": "6702b1e8f9a0b1c2d3e4f5a6", "assistant": "6702b4c1d2e3f4a5b6c7d8e9", "messageCount": 0, "lastMessageAt": null, "createdForTaskId": null, "modelTier": "medium", "createdAt": "2026-11-02T16:30:12.000Z", "updatedAt": "2026-11-02T16:30:12.000Z" } } -
Send Jane's message and run Prime on it.
curl https://api.agnt.ai/chats/6702c5d1e2f3a4b5c6d7e8f9/process \ -X POST \ -H "Authorization: Bearer $AGNT_API_KEY" \ -H "X-User-Email: [email protected]" \ -H "Content-Type: application/json" \ -d '{ "message": { "content": "What is the status of the Brightline Freight renewal?" } }' -
Read the thread.
curl "https://api.agnt.ai/chats/6702c5d1e2f3a4b5c6d7e8f9/messages?perPage=20" \ -H "Authorization: Bearer $AGNT_API_KEY" \ -H "X-User-Email: [email protected]"
POST /chats parameters:
| Name | Type | Required | Description |
|---|---|---|---|
assistantId | string | Yes | One of the user's assistants. |
title | string | No | The chat's title. |
metadata | object | No | Your own key-value data. |
modelTier | string | No | low, medium, high or ultra. Defaults to the organization's chat tier, then the account's, then medium. See Prime. |
POST /chats/:chatId/process parameters:
| Name | Type | Required | Description |
|---|---|---|---|
message | object | No | A user message to save and respond to: content (required), plus optional files ([{ fileId }] from the user's Files), referenceId and metadata. |
triggeringActivityId | string | No | The id of an already-saved message to respond to. |
Omit the body to run on messages already saved. POST /chats/:chatId/messages saves a message without running Prime.
The response is a text/event-stream. The HTTP status is 200 even when the run ends in an error event.
| Event | Data |
|---|---|
status_update | Progress during the run. |
interim_message | { message, activityId }, a message Prime sends before its final reply. |
message | { ok, message }, the reply. |
error | { error, code, status }. |
Each assistant message in GET /chats/:chatId/messages carries a read-only delivery object for outbound messages: state is staged (waiting in an approval), held, provider_accepted (sent and accepted for delivery) or unknown (no confirmed delivery record), with the taskId that sent it and the cartId of its approval, if any.
List and manage chats
curl "https://api.agnt.ai/chats?platform=imessage,sms&perPage=20" \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]"
| Name | Type | Description |
|---|---|---|
platform | string | One channel or a comma-separated list: chat, email, slack, teams, sms, whatsapp, imessage, phone. |
status | string | active (default) or archived. |
assistantId | string | Only chats with this assistant. |
q | string | Search. |
userEmail | string | With an account key and no X-User-Email: only chats of this user. |
page, perPage | integer | Pagination. perPage is at most 100. |
GET /chats/:chatId returns one chat, DELETE /chats/:chatId deletes it, and GET /chats/:chatId/usage returns tokens per run and the credits charged for each run.
Outbound messages
Prime's replies to the user go out directly. Anything sent to someone else, such as an email to [email protected], a text, a Slack message or a call to a third party, is an outbound action and follows the user's autonomy settings. An approved action sends exactly the content that was shown. See Tasks and approvals.
People who are not users of your app are handled by the assistant's stranger settings: talkToStrangers, and the per-install setting on Slack. Unusual requests from them are held for the owner.
Phone calls
A call to the assistant's number is answered with one of its greetings. When the caller is a recognized user, Prime runs the conversation with that user's memory and tasks. Prime places outbound calls when the user asks: to the user, or to a third party to get something done. Outbound calls follow the user's autonomy settings, with separate settings for calling as the assistant and calling as the user. Every attempt records its status, duration, a result summary and a transcript.
curl "https://api.agnt.ai/voice/calls?limit=25" \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]"
{
"ok": true,
"calls": [
{
"source": "direct",
"id": "6702cd59a0b1c2d3e4f5a6b7",
"relationship": "user",
"target": { "phoneNumber": "+14155550187", "displayName": "Jane Rivera" },
"goal": "Brief Jane on the Brightline Freight renewal before the board meeting",
"status": "completed",
"disposition": null,
"startedAt": "2026-11-17T17:00:04.000Z",
"endedAt": "2026-11-17T17:03:12.000Z",
"durationSeconds": 188,
"resultSummary": "Jane approved sending the 2027 terms to Brightline legal.",
"transcript": "Ava: Hi Jane, it's Ava calling about the Brightline renewal."
}
]
}
| Field | Description |
|---|---|
source | direct, or task for an attempt Prime made on a task (with taskId and taskTitle). |
relationship | user, internal (someone in the user's organization) or third_party. |
status | Direct calls: calling, completed, failed, no_answer, voicemail. Task calls: pending, preparing, calling, in_progress, completed, failed, needs_input. |
limit is at most 100.
Greetings
Each assistant has stored opening lines: inbound lines for answering, and outbound templates for calling someone by name. Each list holds 1 to 5 lines of up to 160 characters. Outbound templates contain exactly one {firstName}, and inbound lines contain none. Lines are generated from the assistant's personality, and lines you write are never overwritten by generation.
curl https://api.agnt.ai/assistants/6702b4c1d2e3f4a5b6c7d8e9/voice-greetings \
-X PUT \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inbound": ["Hi, this is Ava with Harborview. How can I help?"],
"outbound": ["Hi {firstName}, this is Ava from Harborview."]
}'
GET /assistants/:assistantId/voice-greetings returns the lines, their source (generated or owner), generating while a generation runs, and the validation limits. POST /assistants/:assistantId/voice-greetings/regenerate takes { "scope": "both" | "inbound" | "outbound", "replaceOwnerEdits": false }, up to 5 times per hour per assistant.
Live voice
Users can talk to their assistant live in the hosted app and in your own apps. Each assistant uses its own engine and voice through voice: { provider, voiceId }. GET /voice/providers lists the engines your account has credentials for, each with its voices and default voice. available is true when an engine can take calls. Engines include live speech-to-speech engines that run on credentials you supply for your own cloud project. GET /voice/providers lists what is available. Voice models follow your account's model tier settings.
Start a session for Jane with Ava, scoped to the Brightline renewal task:
curl https://api.agnt.ai/voice/live/session \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{ "assistantId": "6702b4c1d2e3f4a5b6c7d8e9", "taskId": "6702c3a9b8c7d6e5f4a3b2c1" }'
{
"ok": true,
"provider": "google-live",
"sessionId": "3f6c2a9e-8b41-4d7a-9c15-2e7b0d4a6f18",
"websocketUrl": "wss://voice.agnt.ai/live",
"sessionToken": "lvs_9Qm2xT7rKc4bWz8pLf3nHd6vYa1sEj5u",
"inputSampleRate": 16000,
"outputSampleRate": 24000,
"metadata": {
"accountId": "66f1a2b3c4d5e6f708192a3b",
"userId": "6702b1e8f9a0b1c2d3e4f5a6",
"assistantId": "6702b4c1d2e3f4a5b6c7d8e9",
"taskId": "6702c3a9b8c7d6e5f4a3b2c1"
}
}
| Name | Type | Required | Description |
|---|---|---|---|
assistantId | string | No | Defaults to the assistant of the chat or task. |
chatId | string | No | Scope the session to a chat. |
taskId | string | No | Scope the session to a task. With neither, a new chat is created. |
The client connects to websocketUrl with the single-use sessionToken. POST /voice/live/cancel with { sessionId } cancels a session before it connects.
| Status | Code | Meaning |
|---|---|---|
404 | call_context_not_found | The assistant, chat or task is not this user's. |
409 | voice_engine_not_configured, voice_not_configured | The assistant's voice engine is not set up. |
429 | voice_session_rate_limited | Too many sessions in a short time. |
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /assistants | Assistants visible to the caller, with per-user slack status |
POST | /assistants | Create a shared assistant |
GET PUT DELETE | /assistants/:assistantId | Read, update or delete an assistant |
GET | /assistants/check-email | Whether an address is free (?slug=) |
GET | /assistants/generate | Name and address suggestions (?name=) |
GET PUT | /assistants/:assistantId/voice-greetings | Read or edit greeting lines |
POST | /assistants/:assistantId/voice-greetings/regenerate | Regenerate greeting lines |
GET POST | /users/:userId/assistants | Roster, or create an assistant on it |
POST | /users/:userId/assistants/:assistantId/hire | Add a visible assistant to the roster |
POST | /users/:userId/assistants/:assistantId/primary | Set the primary |
DELETE | /users/:userId/assistants/:assistantId | Release an assistant |
GET POST PUT DELETE | /users/:userId/assistant | The user's primary assistant |
GET POST | /identifiers | List (?type=&userId=) or create identifiers |
GET PUT DELETE | /identifiers/:identifierId | Read, update (verified, label, metadata) or delete |
POST | /identifiers/:identifierId/link | Move to another user |
POST | /identifiers/:identifierId/make-primary | Make an email the user's primary |
POST | /phone-line-requests | Request phone lines |
GET | /phone-line-requests/mine | The user's latest request |
POST | /assistants/:assistantId/slack-apps | Create the assistant's Slack app |
GET | /oauth/slack/assistant/authorize | "Add to Slack" URL |
GET | /oauth/slack/assistant/link | "Sign in with Slack" URL |
GET | /slack/workspaces | The user's linked Slack workspaces |
GET | /oauth/teams/authorize | Connect Microsoft Teams (?target=&targetId=) |
GET POST | /chats | List or create chats |
GET DELETE | /chats/:chatId | Read or delete a chat |
GET POST | /chats/:chatId/messages | List or save messages |
POST | /chats/:chatId/process | Run Prime and stream the reply |
GET | /chats/:chatId/usage | Tokens and credits per run |
GET | /voice/providers | Voice engines and voices |
POST | /voice/live/session | Start a live voice session |
POST | /voice/live/cancel | Cancel a session before it connects |
GET | /voice/calls | The user's call history |