Chat agents
What it is#
The agent that answers your calls answers typed chats too, with the same job, knowledge, actions and backup brain. There are three ways in:
- The chat API, for your own web chat or app: start a conversation, send each message, and get the agent's reply in the response.
- A Telegram bot of your own: connect it once, and the agent answers everyone who writes to it.
- A WhatsApp number or an Instagram professional account on your own Meta app: connect it once, paste two values into the app, and the agent answers everyone who writes to it, within Meta's 24-hour window.
Only an agent with the listen, think, speak setup (pipeline_mode: "cascade") can chat, because its language model writes the replies. A realtime agent has no text step and is refused.
Price#
Each AI reply costs ₹0.05 or $0.0005 from your wallet, in the wallet's currency (price versions inr_chat_reply_2026_11_06_v1 and usd_chat_reply_2026_11_06_v1). Messages people send are free. A reply that was refused, never sent or may not have arrived is never charged; a reply Telegram or Meta accepted stays charged even if it is later reported undelivered.
Your own AI account bills the tokens, as it does on a call; Dvaarik adds nothing to them. On WhatsApp and Instagram, Meta's own charges, if any, are billed to you by Meta, on your own Meta account; we add nothing to them either. A reply holds its price on the wallet when it starts and is charged once, after it was delivered: in the API's response, or once Telegram or Meta accepted its last part. The Money page lists each one as a chat reply (debit_chat_inr or debit_chat_usd).
The chat API#
Conversations take a project key with voice:read and voice:write, the same scopes as calls. The console's twins live under /console/v2/projects/{project_id}/chats.
1. Start a conversation#
curl -sS -X POST https://api.developers.dvaarik.com/v2/chats \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "'"$AGENT_ID"'",
"external_id": "customer-4821",
"variables": { "company": "Northline Dental" }
}'variables fill the agent's job for the whole conversation. external_id is your own id for the person (128 characters at most) and metadata is yours to keep. The 201 is the conversation: keep its id.
2. Send a message and get the reply#
curl -sS -X POST https://api.developers.dvaarik.com/v2/chats/$CHAT_ID/messages \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Idempotency-Key: msg-7f3a" \
-H "Content-Type: application/json" \
-d '{ "text": "Do you open on Sunday?" }'The request waits for the reply (a minute at most) and returns both messages:
{
"message": { "role": "user", "status": "received", "text": "Do you open on Sunday?" },
"reply": {
"role": "agent",
"status": "sent",
"text": "Yes, from 10 in the morning to 2 in the afternoon.",
"currency": "INR",
"money_scale": 100,
"price_minor": "5",
"cost_minor": "5",
"input_tokens": 812,
"output_tokens": 19,
"model": "<your model>",
"tool_calls": []
},
"duplicate": false
}Money is an integer string in the wallet's own unit: paise for rupees, nano-USD for dollars ("500000" is $0.0005). Each action the agent ran on the way is in tool_calls, with what it sent and what it got back.
Send an Idempotency-Key, unique per message. If the connection drops, send the same message with the same key: you get the first reply with duplicate: true, never a second reply and never a second charge.
A reply the agent could not write comes back failed, with a sentence in error and a code in error_code, and costs nothing. Send one message at a time per conversation: the next one after the reply arrives. A message is text, 1 to 4,096 characters.
3. Read conversations#
GET /v2/chats?agent_id=…&channel_id=…&limit=25&offset=0: conversations, the most recent message first;next_offsetreads the next page.GET /v2/chats/$CHAT_ID?limit=50: one conversation with its newest messages, oldest first.next_before, sent back asbefore, reads further back.
When a message is refused#
A refusal stores nothing and charges nothing. Its detail is a sentence that says what happened.
| Status | Why | What to do |
|---|---|---|
402 | The wallet cannot pay for one reply | Top up, then send it again |
409 | Another reply in this conversation is still being written, or the agent cannot answer (switched off, deleted, its AI account not ready, a value missing) | Wait for the reply, or fix the agent |
429 | A reply limit is reached (see Limits), or the project's request limit | Wait Retry-After seconds |
503 | Too many chats on the server right now | Wait Retry-After seconds (2), then send it again with the same key |
What a reply's status means#
status | Meaning |
|---|---|
received | A message from the person |
pending | The agent is writing the reply |
ready | Written, about to be sent to the channel |
sending | Being sent to the channel |
sent | Delivered and charged |
failed | Not sent; error says why. Never charged |
ambiguous | We could not tell whether the channel received it (the request timed out after it was sent). Never sent again and never charged |
A WhatsApp or Instagram reply that missed Meta's 24-hour window is failed with error_code: "window_closed"; see The 24-hour window.
Telegram#
Connect a bot#
1. In Telegram, message @BotFather, send /newbot, and choose a name and a username that ends in bot. 2. Copy the token it gives you. It reads like 123456789:AA…. 3. In the console, open Chats and press Connect a Telegram bot: give it a name, choose the agent and paste the token. Or use a key with channel:write:
curl -sS -X POST https://api.developers.dvaarik.com/v2/chat-channels \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "telegram",
"name": "Support bot",
"agent_id": "'"$AGENT_ID"'",
"bot_token": "'"$TELEGRAM_BOT_TOKEN"'"
}'We check the token with Telegram and point the bot's webhook at us for you (webhook_url in the response), so there is nothing to paste into Telegram. The token is stored encrypted and never returned. A bot connects to one channel at a time, and a project can have 20 channels: bots, WhatsApp numbers and Instagram accounts together.
A bot's agent answers strangers, so it uses each variable's default: an agent with a required variable that has no default is refused. The bot answers private chats with people. Messages in groups and from other bots are recorded and ignored. Photos and other attachments are named to the agent, never opened.
Look after it#
GET /v2/chat-channels(channel:read): each bot'sstatus(active,errorordisconnected),webhook_url,last_error(Telegram's own words about its last failed delivery to us, or ours) andpending_updates.POST /v2/chat-channels/{channel_id}/check: ask Telegram now. It also notices when another app took the bot's webhook over.PATCH /v2/chat-channels/{channel_id}: rename it, answer with another agent, change its limits, or give the same bot's new token after *Revoke current token* in @BotFather (Rotate token in the console).DELETE /v2/chat-channels/{channel_id}: the bot stops answering at once, its token is forgotten and its webhook removed while it still points at us.
When Telegram stops accepting the token, the bot turns error: give it its new token.
WhatsApp#
What you need from Meta#
1. A Meta app at developers.facebook.com, of the Business type, with the WhatsApp product added. 2. Your number on it. In *WhatsApp > API Setup*, add your business phone number. Copy its Phone number ID (not the number itself) and the WhatsApp Business Account ID shown there. 3. A permanent access token. In your business settings, add a system user, give it your app and the WhatsApp Business Account with full control, and generate its token with whatsapp_business_messaging (read and send the number's messages) and whatsapp_business_management (read the number and subscribe your app to it). The temporary token on *API Setup* stops working after a day. 4. The app secret, in *App settings > Basic*. It signs every message Meta sends us, so we know it really came from your app.
Connect it#
In the console, open Chats and press Connect a WhatsApp number. Or use a key with channel:write:
curl -sS -X POST https://api.developers.dvaarik.com/v2/chat-channels \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "whatsapp",
"name": "Front desk number",
"agent_id": "'"$AGENT_ID"'",
"phone_number_id": "'"$WA_PHONE_NUMBER_ID"'",
"waba_id": "'"$WA_BUSINESS_ACCOUNT_ID"'",
"access_token": "'"$WA_SYSTEM_USER_TOKEN"'",
"app_secret": "'"$META_APP_SECRET"'"
}'We read the number with your token first: a token Meta refuses, or a number it cannot read, is never stored. With waba_id, we also subscribe your app to the account's messages for you (meta.subscription says how it went); without it, make sure your app is subscribed yourself. The token and the secret are stored encrypted and never returned. The 201 carries what to paste:
{
"provider": "whatsapp",
"status": "active",
"account_id": "106540352242922",
"account_name": "Northline Dental",
"webhook_url": "https://api.developers.dvaarik.com/chat/whatsapp/<channel id>/<guard>",
"meta": {
"verify_token": "<48 characters>",
"webhook_object": "whatsapp_business_account",
"webhook_fields": ["messages"],
"waba_id": "102290129340398",
"display_phone_number": "+91 98765 43210",
"verified_name": "Northline Dental",
"subscription": { "ok": true, "at": "2026-11-08T09:00:00Z", "error": null }
}
}Paste the callback URL and verify token#
In your Meta app, open *WhatsApp > Configuration*. Under Webhook, paste webhook_url as the callback URL and meta.verify_token as the verify token, press Verify and save, then subscribe to the messages field. The verify token never changes, even when you give the channel a new token or secret.
A Meta app sends all its WhatsApp messages to one callback URL, so connect one number per Meta app. Messages for another number of the same app are ignored and noted on the channel. A message sent from a WhatsApp number that is itself connected here as a channel (yours or anyone's) is ignored, so two agents never answer each other in a loop. While the app is in Development mode, Meta may not send us your customers' messages: switch it to Live before real people write (Meta may ask for App Review of the two permissions first).
What WhatsApp says after a reply#
A sent reply carries delivery_status as Meta reports it: sent, delivered, read or failed. It only moves forward. A reply is charged once Meta accepts it, so a later failed is recorded on it and keeps its charge.
Instagram#
What you need from Meta#
1. An Instagram professional account (Business or Creator). 2. A Meta app of the Business type with Instagram added, set up with Instagram login (*API setup with Instagram login*). 3. A long-lived access token. On that page, add your account and generate its token with instagram_business_basic (read the account) and instagram_business_manage_messages (read and send its messages). It lasts 60 days, and we renew it for you. 4. The account ID and the app secret. The account's ID is the user_id that GET graph.instagram.com/me returns for the token, shown beside it in the app. The app secret is in *App settings > Basic*; you may also give the Instagram app secret (instagram_app_secret). A message signed with either is accepted.
Connect it#
Press Connect an Instagram account on the Chats page, or:
curl -sS -X POST https://api.developers.dvaarik.com/v2/chat-channels \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "instagram",
"name": "Shop Instagram",
"agent_id": "'"$AGENT_ID"'",
"instagram_account_id": "'"$IG_ACCOUNT_ID"'",
"access_token": "'"$IG_ACCESS_TOKEN"'",
"app_secret": "'"$META_APP_SECRET"'"
}'The token must belong to the account you name; a token of another account is refused with a sentence that says which one it is. We subscribe your app to the account's messages for you, every time (meta.subscription). Then, in your Meta app's *API setup with Instagram login*, find Configure webhooks, paste webhook_url and meta.verify_token (object instagram), press Verify and save and subscribe to the messages field. One account per Meta app, and the app must be Live for Meta to send messages from everyone. Instagram does not tell us who else is a channel here, so between two Instagram agents only the replies-an-hour limit stops a loop.
The token renews itself#
meta.token_expires_at says when the token runs out. We renew it well before: from 20 days ahead, retrying every few hours. Meta renews a token only once it is a day old, so right after connecting the date is unknown until the first renewal. If Meta refuses the token, the channel turns error; if renewal still fails a week before it runs out, last_error says so. Either way you get chat.channel.attention: log in again and give the channel its new token.
The 24-hour window#
Meta lets a business answer freely only within 24 hours of the person's last message. A WhatsApp or Instagram reply after that is not sent: it ends failed with error_code: "window_closed" and costs nothing. We check before the agent writes (so no tokens are spent) and again before each part is sent. The agent answers again as soon as the person writes. Telegram and the chat API have no such window.
Look after a WhatsApp number or an Instagram account#
GET /v2/chat-channels: each channel'sstatus,last_error,meta(what to paste and what Meta says about the account) and limits. Never a token or a secret.POST /v2/chat-channels/{channel_id}/check: read the account with the stored token now and subscribe your app again. A refused token turns the channelerror, with Meta's reason inlast_error.PATCH /v2/chat-channels/{channel_id}: rename it, change the agent or the limits, or give the same number's or account's newaccess_token,app_secret,instagram_app_secretorwaba_id. Only what you send changes. A token of another number or account is refused: connect that one as a new channel. If another request changed the channel's credentials while yours was checked, you get a409: read the channel again and send yours again.DELETE /v2/chat-channels/{channel_id}: it stops answering at once and its token and secrets are erased. Nothing changes in your Meta app: point its callback URL elsewhere if you use it for other software.
| Refusal | Why |
|---|---|
422 | A field is missing or the wrong shape (the item names the field and never repeats what you sent), or Meta refused the token, cannot read the number, or the token belongs to another account; detail says which |
409 | That number or account is already connected, the project has its 20 channels, the channel is disconnected, or another request changed its credentials while yours was checked (read it again and retry) |
last_error also carries what we notice on delivery: a message whose signature does not match the app secret saved here (give the channel that app's secret), messages for another number or account of the same app, or a correctly signed delivery we could not read (answered, so Meta stops sending it again, then dropped).
Limits#
| Limit | Default | Set on | Most |
|---|---|---|---|
| Replies a minute | 30 | each channel | 300 |
| Replies a day | 2,000 | each channel | 50,000 |
| Replies an hour in one conversation | 60 | each channel | 300 |
| Messages a minute from one person | 20 | fixed | — |
| Replies of one project's channels written at the same time | 2 | fixed | — |
Chat API conversations share the defaults, per project. Only replies that were written count. Past a limit, the API answers 429; a channel writes nothing to the person and sends you chat.reply.failed. Messages beyond 20 a minute from one person are ignored as a flood. Messages that arrive close together (about a second and a half apart) get one reply. A reply is 4,000 characters at most and goes out in parts a second apart: Telegram and WhatsApp take 4,096 characters a part (WhatsApp counts an emoji as two), Instagram 1,000 bytes (a reply in Hindi or another non-Latin script takes several parts, never cut inside a letter).
How long messages are kept#
Each message is deleted on its own clock:
- after the agent's
retention_days(90 days at most) when the agent hasstore_transcripton; - 24 hours after it arrived when
store_transcriptis off (the default), which is long enough to answer.
A conversation is deleted once it has no messages left. Everything else is on Your data.
Events#
Project webhooks subscribed to chat.* or * get four chat events. call.* does not include them.
| Event | When |
|---|---|
chat.message.received | A person's message arrived |
chat.reply.sent | A reply was delivered and charged |
chat.reply.failed | A reply was not sent, or may not have arrived (ambiguous: true). Never charged |
chat.channel.attention | A channel needs you: its token was refused, or an Instagram token could not be renewed a week before it runs out |
The body is described under Chat events.
In the console#
Chats starts with where your agents chat: a big button each for WhatsApp, Instagram and Telegram. A WhatsApp or Instagram form lists what you need from Meta first; once connected, its card shows the callback URL and verify token with one-click copy, whether your app is subscribed, an Instagram token's life, and Check, Edit, Change token or secret, and Disconnect. Telegram bots have Check, Edit, Rotate token and Disconnect. Below them is every conversation, read as a chat with each reply's status, what WhatsApp said after it was sent, cost and tokens. Try a chat on an agent card types to the agent through the console, exactly as your app would through the chat API.
Use one project per boundary
Provider secrets stay encrypted and project API keys stay on your server.