# Amomic-AI — complete documentation > Generated from https://docs.amomic.in · 2026-08-01 > This file contains every documentation page in full, in reading order. ============================================================================ # SECTION: Get started ============================================================================ ---------------------------------------------------------------------------- ## Quickstart URL: https://docs.amomic.in/start/quickstart ---------------------------------------------------------------------------- This walks you from an empty workspace to an agent answering real questions on your website. It takes about five minutes and needs nothing installed. If you'd rather start from your own backend, go to [Your first API call](/start/first-api-call) instead — but do this first anyway. The API sends messages *from* an agent, so you need an agent before the API is useful. ::: steps ::: step Create an agent In the console, open **Agents** and choose **New agent**. Two fields matter: - **Agent name** — what your team calls it. `Support Agent` is fine. - **Purpose** — what outcome this agent owns. Describe the job, not the channel: > Resolve common support questions using approved company information. The purpose is not decoration. It becomes the spine of the agent's instructions, and a vague purpose produces a vague agent. "Help customers" is vague. "Answer delivery, refund, and order-status questions from our published policy, and hand off anything about billing" is not. Leave **Business knowledge** on *Require a ready knowledge base before publishing*. That setting is what stops an agent going live with nothing to stand on. ```mock {"component": "AgentsScreen", "props": {"caption": "Agents you have built, with where each one is running."}} ``` ::: ::: step Give it something to know An agent with no knowledge can only be polite. Open **Knowledge**, create a knowledge base, and add your first source. The fastest useful source is your own website: - Choose **Website**, paste your help or FAQ URL, and let it crawl. - Crawling reads the pages, splits them into passages, and indexes each passage by meaning rather than by keyword. You can also upload a PDF or paste text directly. Most workspaces end up with all three. ::: tip Start narrower than feels right Point it at your help centre, not your entire marketing site. Retrieval quality falls when the index is full of pages that answer nothing — press releases and careers pages compete with the passage that actually holds the refund window. ::: Back on the agent, open the **Knowledge** tab and bind the knowledge base to it. An agent only sees knowledge you have explicitly attached. ::: ::: step Test it before anyone else does Open the **Test** tab and ask it the five questions your customers actually ask. Not the easy ones — the ones your team gets wrong. Watch for two failures specifically: - **It answers from nowhere.** If it states a policy that isn't in your sources, your knowledge base is thin and the agent is filling the gap. Add the missing source. - **It refuses something it should know.** The passage exists but isn't being retrieved — usually because the page buries the answer in a table or an image. Fix these now. Every one of them is a real customer conversation later. ::: ::: step Publish a version Testing edits a draft. Publishing freezes it. Choose **Publish** and the current state of the agent becomes an immutable **version** — persona, instructions, knowledge bindings, and tool policy, captured together. Your next edit starts a new draft and does not touch what is running. This is the mechanism that makes the rest of the system safe: nothing you type in the studio can change an agent that is currently talking to customers. ::: ::: step Deploy it to your website From the agent, choose **Deploy** → **Web widget**. The console gives you one line: ```html title="Paste before " ``` Paste it before the closing `` tag of your site. That's the whole installation — no npm package, no build step, no framework plugin. ```mock {"component": "WidgetMock", "props": {"caption": "The widget on a customer's screen. Colours, greeting, and position all follow your configuration."}} ``` Add your domain to the widget's allowed list while you're there. Until you do, a draft bot answers on any origin — convenient for testing, wrong for production. ::: ::: ## You now have - An agent with a defined job, grounded in your own content - A published version you can point to and roll back to - A live deployment on one channel, with every conversation landing in your inbox ## Where to go next ::: cards ::: card Put it on WhatsApp The same agent, same knowledge, answering on [WhatsApp](/channels/whatsapp). Connect your own WhatsApp Business number through Meta. ::: ::: card Give it a phone number Let it [answer the phone](/channels/voice) in English, Hindi, or whichever Indian language the caller uses. ::: ::: card Drive it from your backend [Send messages and place calls](/start/first-api-call) from your own systems over the REST API. ::: ::: card Understand the model [How it fits together](/concepts/architecture) — the ten minutes that make everything else obvious. ::: ::: ---------------------------------------------------------------------------- ## Your first API call URL: https://docs.amomic.in/start/first-api-call ---------------------------------------------------------------------------- The API lets your own systems do what your team does in the console: send a message, place a call, read a conversation, open a ticket. This page takes you from nothing to a real message delivered. ::: note Server-side only `/api/v1` sends no CORS headers, by design. An API key in front-end JavaScript is a leaked API key — anyone can read it out of your bundle. Call this API from your backend. For in-page use there is the [web chat widget](/channels/web-chat), which has its own origin-scoped auth. ::: ## 1. Create a key In the console, open **Developers → API keys** and choose **Create key**. Name it after the system that will hold it — `Order service`, not `Test key 2`. When a key starts behaving strangely at 3 a.m., the name is what tells you which deployment to look at. New keys are read-only by default (`conversations:read` and `agents:read`). Widen them deliberately: a key is easy to grant more scope later and impossible to un-leak. See [Scopes](/api/scopes). ```mock {"component": "ApiKeysScreen", "props": {"caption": "Keys are shown masked after creation. The one full copy is the only one."}} ``` ::: warning The secret is shown exactly once We store a SHA-256 hash, not the key. Nobody at Amomic-AI can recover it for you — not support, not an engineer with database access. Put it in your secret manager before you close the dialog. If you lose it, roll the key; rolling gives you a new secret and keeps the old one working for 24 hours so you can deploy without downtime. ::: ## 2. Verify it `GET /ping` costs nothing, changes nothing, and tells you everything about how the API sees your key. It should be the first call every integration makes. ::: codegroup ```bash cURL curl https://services.amomic.in/api/v1/ping \ -H "Authorization: Bearer $AMOMIC_API_KEY" ``` ```javascript Node.js const response = await fetch("https://services.amomic.in/api/v1/ping", { headers: { Authorization: `Bearer ${process.env.AMOMIC_API_KEY}` }, }); console.log(await response.json()); ``` ```python Python import os, httpx response = httpx.get( "https://services.amomic.in/api/v1/ping", headers={"Authorization": f"Bearer {os.environ['AMOMIC_API_KEY']}"}, ) print(response.json()) ``` ::: ```json title="200 OK" { "ok": true, "workspace_id": "ten_01JQRS4T8XZ0AB1CD2EF3GH4", "key_name": "Order service", "environment": "live", "scopes": ["agents:read", "conversations:read", "messages:send"] } ``` Read `scopes` carefully. Most "why is this 403" questions are answered right here, before any real request is made. ## 3. Find out what your workspace can do Ids don't exist in a vacuum — you need a sender to send *from*. `GET /channels` exists precisely because a backend integrator has no console open to copy one out of. ```bash curl https://services.amomic.in/api/v1/channels \ -H "Authorization: Bearer $AMOMIC_API_KEY" ``` ```json title="200 OK" { "message_channels": ["whatsapp"], "call_channels": ["voice"], "senders": [ { "channel": "whatsapp", "sender_id": "was_9f2c41ab77de1063c8a4b512", "display_number": "+919461093306", "verified_name": "Acme Retail", "is_default": true, "status": "connected" } ] } ``` ## 4. Send a real message Now the interesting one. This sends an actual WhatsApp message to an actual phone. ::: codegroup ```bash cURL curl -X POST https://services.amomic.in/api/v1/messages \ -H "Authorization: Bearer $AMOMIC_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-4821-shipped" \ -d '{ "to": "+919876543210", "template": { "name": "order_shipped", "language": "en_US", "variables": ["Asha", "AM-4821"] } }' ``` ```javascript Node.js const response = await fetch("https://services.amomic.in/api/v1/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.AMOMIC_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": `order-${order.id}-shipped`, }, body: JSON.stringify({ to: order.customerPhone, template: { name: "order_shipped", language: "en_US", variables: [order.customerName, order.reference], }, }), }); if (!response.ok) { const { error } = await response.json(); throw new Error(`${error.code}: ${error.message}`); } ``` ```python Python import os, httpx response = httpx.post( "https://services.amomic.in/api/v1/messages", headers={ "Authorization": f"Bearer {os.environ['AMOMIC_API_KEY']}", "Idempotency-Key": f"order-{order.id}-shipped", }, json={ "to": order.customer_phone, "template": { "name": "order_shipped", "language": "en_US", "variables": [order.customer_name, order.reference], }, }, ) response.raise_for_status() ``` ::: ```json title="201 Created" { "message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSMkI5RUE4...", "channel": "whatsapp", "to": "+919876543210", "status": "sent" } ``` ```mock {"component": "WhatsAppMock", "props": {"caption": "What lands on your customer's phone. The variables you passed are substituted into the approved template."}} ``` Three things in that request are doing real work: - **`template` rather than `text`.** You can only send free-form text inside the 24-hour window that opens when a customer messages you. An order update almost never falls inside it. [The 24-hour window](/channels/whatsapp-window) explains when each applies. - **`Idempotency-Key`.** Your retry after a network timeout replays the stored response instead of messaging the customer twice. See [Idempotency](/api/idempotency). - **`variables` positionally.** `["Asha", "AM-4821"]` fills `{{1}}` and `{{2}}`. An object like `{"1": "Asha", "2": "AM-4821"}` works too. ## 5. Handle the failures properly Every error uses the same envelope, so one handler covers the whole API: ```json title="412 Precondition Failed" { "error": { "code": "failed_precondition", "message": "This workspace has no WhatsApp sender connected.", "details": { "reason": "channel_not_connected" }, "request_id": "req_01JQRS4T8XZ0AB1CD2EF3GH4", "correlation_id": "cor_01JQRS4T8XZ0AB1CD2EF3GH4" } } ``` Branch on `error.code` for the class of problem and on `error.details.reason` for the specific cause. Log `request_id` — quoting it to support turns a support thread into a lookup. The ones you will actually hit first: ```responses [ {"status": 401, "code": "unauthenticated", "reason": "wrong_environment", "description": "A test key was used against production, or the reverse. Not a bad secret."}, {"status": 403, "code": "permission_denied", "reason": "missing_scope", "description": "The key is valid but lacks the scope. `details.required_scope` names it."}, {"status": 404, "code": "not_found", "reason": "template_not_found", "description": "No approved template by that name. Check spelling and approval status."}, {"status": 409, "code": "conflict", "reason": "session_window_closed", "description": "Free-form text outside the 24-hour window. Send a template."}, {"status": 412, "code": "failed_precondition", "reason": "channel_not_connected", "description": "No WhatsApp sender is connected to this workspace yet."} ] ``` [Errors](/api/errors) has the complete list. ## Test keys and live keys There is one production API. Keys carry an environment and are checked against the host they are used on: ```params [ {"name": "amk_live_…", "type": "live key", "required": false, "description": "Works against services.amomic.in. Real messages, real calls, real money."}, {"name": "amk_test_…", "type": "test key", "required": false, "description": "Issued in non-production environments. Sending one at the production host returns 401 with wrong_environment."} ] ``` A mismatch is always a 401 with `details.reason` of `wrong_environment` — never a silent failure and never a bad-secret error. If you see it, you have the right key pointed at the wrong host. ## Next ::: cards ::: card Place an AI call [POST /calls](/api/calls) has your agent phone someone and talk to them. ::: ::: card Receive events [Webhooks](/webhooks/overview) push replies, call outcomes, and new tickets to you instead of you polling. ::: ::: card A complete flow [Order-shipped update](/recipes/order-updates) — the whole thing end to end, including retries and the webhook that closes the loop. ::: ::: card Hand it to a coding agent [Use with agents](/agents-setup) connects these docs to Claude Code or Codex so it writes the integration with the real contract in front of it. ::: ::: ---------------------------------------------------------------------------- ## What you can build URL: https://docs.amomic.in/start/what-you-can-build ---------------------------------------------------------------------------- This page is for deciding whether this fits. Every scenario below is built from things that ship today — no roadmap, no "with a bit of custom work". Each one names the pages that build it, so you can skim the shape and then go deep on whichever is yours. Underneath all six is one model: you build **one agent**, publish it as a **version**, and deploy that version onto each **channel** where customers reach you. Read [How it fits together](/concepts/architecture) once and every scenario here becomes obvious. ## A support agent on WhatsApp and the web The most common first build, and the one that pays for itself fastest. You point a knowledge base at your help centre, upload the two PDFs that were never turned into pages, and paste in the handful of exceptions your team knows by heart. You write a purpose that names what the agent owns and what it hands off. Then you deploy that one agent twice — once as a widget on your site, once on your WhatsApp Business number. Same knowledge, same answers, two touchpoints. When your refund window changes from 7 days to 14, you update one source and both channels are correct within a re-sync. The work is mostly in the knowledge, not the configuration. Expect to spend an hour on sources and testing, and about ten minutes on everything else. → [Quickstart](/start/quickstart) · [Knowledge bases](/build/knowledge) · [Install the widget](/channels/web-chat) · [Set up WhatsApp](/channels/whatsapp) ## An AI receptionist on your main line Your published number rings; the agent answers, in Hindi or English, and follows the caller if they switch mid-sentence. You get a phone number, deploy an agent to it, and write the opening line callers hear the moment the call connects. The **call language** setting decides what a caller may speak — English only for the cleanest transcription, Hindi and English for the usual Indian mix, or all ten supported Indian languages — and the **opening language** decides only which one the first sentence is in. On a call the agent answers common questions instantly from a compressed fact sheet built from your knowledge base, and reaches for a full search only when a caller asks for something specific. When it is done, it says goodbye and hangs up properly rather than leaving a caller listening to an open line. Every call produces a transcript, an outcome, response-latency figures, and optionally a recording. → [How voice works](/channels/voice) · [Numbers and KYC](/channels/phone-numbers) · [Languages](/channels/voice-languages) ## Order and delivery notifications from your backend This one is not conversational at all. Your systems already know when an order ships; the customer's phone is where they want to hear about it. Your backend calls [`POST /messages`](/api/messages) with an approved WhatsApp template and the values to fill in. Because a shipment notification almost always fires outside the 24-hour window, a template is not optional — a free-form send there is refused with a `409` and `session_window_closed`, which is a rule of WhatsApp's, not ours. Send an `Idempotency-Key` derived from your own order id and a retry after a network timeout returns the original response instead of messaging the customer twice. If the customer replies, the window opens, your agent picks up the conversation, and it lands in the same inbox as everything else. → [Order-shipped update](/recipes/order-updates) · [Sending messages](/api/messages) · [Templates](/channels/whatsapp-templates) · [The 24-hour window](/channels/whatsapp-window) · [Idempotency](/api/idempotency) ## An outbound renewal reminder, triggered by your CRM A policy is expiring in seven days. Your CRM knows; a human calling three hundred customers does not scale. Your system calls [`POST /calls`](/api/calls) with the destination, the caller-ID number to dial from, and a short `purpose` that tells the agent why it is calling. The agent opens with a fixed template that identifies your business, discloses that it is a virtual assistant, and asks whether now is a good time — in that order, deliberately. From there it is a real conversation: it can answer questions from your knowledge base and open a ticket if the customer wants a person. A `201` means the call is ringing, not connected. The outcome arrives later, on the `call.ended` webhook or by fetching the call. ::: warning Outbound calling is your compliance decision The platform enforces the operational guards: the destination cannot be one of your own numbers, the caller ID must be a number your workspace owns with an agent deployed on it, and concurrent calls are capped per workspace. It does **not** check a do-not-call registry, enforce calling hours, apply a per-day cap, or maintain a consent register — none of those exist. Whether a given number should be called, and when, is entirely on your side of the line. ::: → [Trigger a call from your CRM](/recipes/crm-call) · [Placing calls](/api/calls) · [Outbound calls](/channels/outbound-calls) ## Escalation into tickets when a human is needed The agent's most useful skill is knowing when to stop. When a customer asks for a person, asks for follow-up, or has a problem the knowledge base cannot resolve, the agent calls `create_support_ticket`. That one action resolves the customer against your contact directory — by email, phone, or WhatsApp ID, depending on what the channel already knows — opens the ticket with the issue summary and the conversation reference attached, and reads the reference back to the customer: `AM-4F91C2E0`. Retries inside the same conversation return the same ticket rather than a duplicate. Your team works it in the console; your backend hears about it on the `ticket.created` webhook. This is the hand-off path, and it is worth being explicit about why: the inbox is read-only. There is no takeover control and no way to join a live conversation as a human. When a person needs to get involved, a ticket is how they do it. → [Escalate to a ticket](/recipes/support-escalation) · [Tools and actions](/build/tools) · [Tickets](/api/tickets) ## Conversations synced into your warehouse Every channel converges into one conversation store, which makes it a genuinely useful analytics source — but only if it is in your warehouse next to everything else. A scheduled job walks [`GET /conversations`](/api/conversations) newest-first with a cursor, fetches the transcript for anything new, and writes both. The transcripts you get back contain only customer and agent messages; the internal trail of what the agent looked up is not included, so you are not accidentally exporting operational noise or tool payloads into a table someone will later query. A read-only key carrying only `conversations:read` is all this needs — build the job against a key that cannot send anything. → [Sync conversations](/recipes/sync-conversations) · [Conversations API](/api/conversations) · [Pagination](/api/pagination) · [Scopes](/api/scopes) ## What this is not for Worth saying plainly, because discovering it in week three is expensive. **This is not a bulk marketing blaster.** There is no campaign builder, no broadcast tool, no contact-list send, no batch endpoint, and no scheduling parameter anywhere in the API. Sending to a thousand people means a thousand individual calls, each subject to a per-key rate limit — the shape of the product is one customer at a time. WhatsApp's own opt-out keywords are honoured automatically and a suppressed recipient is refused, which is correct behaviour for a support and transactional tool and an obstacle if what you wanted was reach. If your use case is promotional volume, this is the wrong tool and it will be a frustrating one. **This is not a replacement for your CRM.** Contacts here are a directory for *reaching* people — name, phone, email, WhatsApp ID, per-channel consent, tags. There are no deals, no pipelines, no custom fields, no automatic activity timeline, and no merge for duplicates. It is deliberately the minimum needed to know who you are talking to and whether you are allowed to contact them. Your CRM stays the system of record; this stays the system that talks to customers, and [`POST /contacts`](/api/contacts) is the seam between them. **And two smaller ones.** There is no human takeover in the inbox — escalation is a ticket. And there is no per-channel agent behaviour: one agent behaves the same on WhatsApp, the web, and the phone, so genuinely different jobs need genuinely different agents. ## Where to start ::: cards ::: card Build the first one [Quickstart](/start/quickstart) — an agent answering on your website in about five minutes. ::: ::: card Wire up your backend [Your first API call](/start/first-api-call) — a working authenticated request in about a minute. ::: ::: card Understand the model first [How it fits together](/concepts/architecture) — five nouns that make everything else obvious. ::: ::: card Look up a word [Glossary](/concepts/glossary) — every term in these docs, defined with a link. ::: ::: ============================================================================ # SECTION: Concepts ============================================================================ ---------------------------------------------------------------------------- ## How it fits together URL: https://docs.amomic.in/concepts/architecture ---------------------------------------------------------------------------- Everything in this documentation is an elaboration of five nouns: a **workspace** holds **agents**; an agent is published as a **version**; a version is put on a **channel** by a **deployment**. Read this page once and the rest of the docs stop being a list of features and start being obvious. ## The whole model in one sentence You build one agent, you freeze it as a version, and you deploy that version onto each channel where customers can reach you. Every one of those channels feeds back into the same inbox, the same customer directory, and the same analytics. ```mock {"component": "PlatformDiagram", "props": {"caption": "One agent, published as versions, deployed onto channels. Customers arrive on the left; your systems talk to the API on the right."}} ``` ## A workspace A workspace is your organisation. It owns the agents, the knowledge, the channel connections, the conversations, the contacts, the API keys, and the bill. It is also the isolation boundary. Every query the platform runs is filtered by workspace before anything else happens, and there is no mechanism — no sharing setting, no cross-workspace agent, no shared knowledge base — for data to cross it. A WhatsApp number connected to one workspace cannot be connected to another. Two workspaces are two products as far as the data is concerned. Everything else on this page happens *inside* one workspace. [Workspaces and roles](/concepts/workspace) covers who can do what within it. ## An agent is a persona, knowledge, and tools An agent is not a chatbot script and not a flow chart. It is three things bundled together: | Part | What it is | Where you set it | |---|---|---| | **Persona** | Its name, its purpose, its instructions, the language it works in, and how strictly it must stick to your content. | The agent's **Persona** tab | | **Knowledge** | The knowledge bases you have explicitly attached. An agent can only answer from knowledge you attached to it. | The agent's **Knowledge** tab | | **Tools** | The things it can *do* rather than know — open a ticket, send a WhatsApp message, call your API, end a call. | The agent's **Tools** tab | Those three things are channel-neutral. You do not write a WhatsApp agent and a phone agent; you write one agent, and each channel renders it appropriately — the same answer arrives as light markdown in the web widget, as WhatsApp's own bold and italic syntax on WhatsApp, and as plain spoken prose on a call. That is the first reason the model is shaped this way: **you describe the job once**. When your refund window changes from 7 days to 14, you update one knowledge source and every channel is correct. See [Agents and versions](/build/agents), [Knowledge bases](/build/knowledge), and [Tools and actions](/build/tools). ## Publishing freezes a version While you are editing, you are editing a draft. Nothing you type is live. When you choose **Publish**, the current state of the agent is captured as a **version**: the persona, the knowledge bindings, and the tool policy, all snapshotted together and stamped with a sequence number and a configuration hash. That version is immutable. There is no edit path for a published version — not in the console, not in the API, not for support. The only way to change anything is to publish a new one. Your next edit starts a fresh draft. The agent's badge changes from `Published` to `Changes to publish`, and the version that is actually running does not move. This is the second reason the model is shaped this way: **you can fix an agent without touching what is live**. You can rewrite the instructions, re-crawl the website, attach a new knowledge base, and test all of it at midday on a Tuesday while the version deployed on your phone number keeps answering exactly as it did this morning. Nothing you do in the studio can reach a customer until you publish and deploy on purpose. ::: note Immutability is what makes rollback meaningful If versions could be edited, "roll back to version 3" would mean "roll back to whatever version 3 has become". Because they cannot, version 3 is still exactly the agent that worked, including which knowledge it was allowed to see. ::: ## Deploying puts a version on a channel A **channel** is a kind of touchpoint: web chat, WhatsApp, or voice. A **channel connection** is a specific one you own and have verified — this website's domain, that WhatsApp Business number, this phone number. A **deployment** is the join between them: *this agent version, on that connection, with this channel-specific configuration.* ``` agent version + channel connection + channel config = deployment (what it says) (where customers (greeting, call reach you) language, domains) ``` The channel config is the part that genuinely differs per channel and has nowhere else to live: the opening line a caller hears, which language the call opens in, whether calls are recorded, which domains the widget may load on, which WhatsApp sender number it answers from. One agent can have many deployments — a widget on your main site, a second widget on your help centre, a WhatsApp number, and a phone number, all serving the same published version. Each is independent. Pausing the phone deployment does not touch the widget. A deployment moves through `draft` → `ready` → `active`, and when a newer deployment takes over the same connection, the old one becomes `superseded`. Activation is a single transaction that swaps the connection over and supersedes whatever was there, so two people activating at once cannot both win. The rule worth learning early: **editing a live deployment clones it rather than changing it.** An active deployment is frozen; a change produces a replacement that must validate before it can replace the original. A bad edit can therefore never take down a working number — the worst case is a clone that refuses to activate, while the old deployment carries on. [Deployments](/build/deployments) covers the whole lifecycle. ## Everything converges Whatever channel a customer arrives on, the same three things happen. **One inbox.** Every conversation — a widget chat at 2 a.m., a WhatsApp thread, a phone call transcript — lands in the same inbox with its full transcript, tagged with the channel it came from. You do not check three places. **One customer directory.** When an agent needs to identify someone — to open a ticket, say — it resolves them against your contacts by email, phone, and WhatsApp ID before creating anything new. A returning customer enriches the record that already exists rather than spawning a duplicate. **One set of analytics.** Conversation volume, messages per visitor, channel mix, and voice performance are computed across every deployment of every agent, and can be filtered down to a single touchpoint. [Conversations and contacts](/concepts/conversations) explains the transcript and identity model in detail, including where the identity story is still incomplete. ## The two ways in There are exactly two interfaces, and they are split along a deliberate line. ### The console, for humans `console.amomic.in` is where the things you change once a quarter live: creating agents, writing personas, adding knowledge, configuring tools, publishing versions, connecting channels, deploying, reading the inbox, working tickets, managing the team and the bill. These are decisions that want review, permissions, and a visible history — which is exactly what a UI provides and an API does not. ### The API, for your systems `https://services.amomic.in/api/v1` is where the things you call ten thousand times a day live: sending a message, placing a call, reading a conversation, opening a ticket, upserting a contact. It is a small, flat REST surface, server-side only, authenticated with a workspace-scoped key. Start at [Your first API call](/start/first-api-call) or the [API overview](/api/overview). ### Which one does what | Task | Console | API | |---|---|---| | Create or edit an agent | Yes | — | | Add or refresh knowledge | Yes | — | | Configure tools and the tool policy | Yes | — | | Publish a version | Yes | — | | Deploy, pause, or replace a deployment | Yes | — | | Connect a WhatsApp number, request a phone number | Yes | — | | Create or revoke an API key | Yes (admins) | — | | Send a WhatsApp message | The agent does it in conversation | [`POST /messages`](/api/messages) | | Place an outbound AI call | From a ticket | [`POST /calls`](/api/calls) | | Read a conversation transcript | Inbox | [`GET /conversations`](/api/conversations) | | Open a support ticket | Tickets | [`POST /tickets`](/api/tickets) | | Create or match a contact | Contacts | [`POST /contacts`](/api/contacts) | | Be told when something happens | Notifications | [Webhooks](/webhooks/overview) | The API has no agent-building routes and will not grow them. Configuration that changes behaviour for every future customer belongs where a person can review it; traffic belongs on the API. ::: warning The API is not callable from a browser `/api/v1` sends no CORS headers at all. API keys are workspace-scoped, so a key in front-end JavaScript is a leaked workspace. For anything in the page, use the [web chat widget](/channels/web-chat), which has its own origin-scoped auth and no key. ::: ## Following one customer through Priya opens your site at 11 p.m. and asks about a delayed order. 1. The **widget** loads on an allowed domain and finds the **deployment** attached to it. That deployment names an **agent version**. 2. The version's **knowledge bindings** decide what the agent may search. Her question is matched against passages by meaning, and the closest ones are put in front of the model. 3. The agent answers from those passages. Because it cannot resolve the delay itself, it reaches for a **tool** — `create_support_ticket` — which first resolves Priya against your **contacts** by email, then opens a ticket and returns its public reference, `AM-4F91C2E0`. 4. The whole exchange is a **conversation** in your **inbox**, with the ticket linked to it. 5. Your backend hears about it on the `ticket.created` **webhook**, and your ops tool calls [`POST /messages`](/api/messages) the next morning to send her the resolution on WhatsApp — the same person, matched on the same phone number. Five nouns, one customer, no duplicated configuration anywhere. ## What is deliberately not in the model Naming the gaps is faster than discovering them. - **There is no per-channel agent.** You cannot give WhatsApp different instructions from voice. If two channels genuinely need different behaviour, build two agents and deploy one to each. - **There is no per-deployment knowledge override.** Knowledge is bound to the agent and frozen into the version; every deployment of that version sees the same knowledge. - **There is no cross-workspace sharing.** Agents and knowledge bases do not travel between workspaces. - **Versions are never edited.** Not by you, not by support. Publish a new one. - **Channel connections are not created by the API.** Numbers and domains are provisioned and verified in the console. ## Where to go next ::: cards ::: card Workspaces and roles [Who can do what](/concepts/workspace), how invitations work, and why API keys are workspace-wide. ::: ::: card Build an agent [Agents and versions](/build/agents) — the fields that matter, and how to write a purpose that produces a good agent. ::: ::: card Teach it something [Knowledge bases](/build/knowledge) — the five source types, how retrieval works, and how to get good answers out of it. ::: ::: card Put it somewhere [Deployments](/build/deployments) — the lifecycle, activation, and why editing a live deployment clones it. ::: ::: card Look up a word [Glossary](/concepts/glossary) — every term in these docs, defined in two sentences with a link. ::: ::: card Drive it from code [Your first API call](/start/first-api-call) — a working request in about a minute. ::: ::: ---------------------------------------------------------------------------- ## Workspaces & roles URL: https://docs.amomic.in/concepts/workspace ---------------------------------------------------------------------------- A workspace is your organisation inside Amomic-AI. It owns your agents, knowledge bases, channel connections, conversations, tickets, contacts, API keys, and the subscription that pays for all of it. Everything you build lives in exactly one workspace, and a person can belong to more than one — the topbar switcher moves between them. Two things make the workspace worth understanding on its own: it is the **billing boundary**, and it is the **isolation boundary**. ## The isolation boundary Every read and write the platform performs is filtered by workspace before anything else is evaluated. There is no sharing setting, no "make this knowledge base public", and no cross-workspace agent. Concretely: - An agent, a knowledge base, or a published version belongs to one workspace and cannot be copied to another from the product. - A WhatsApp Business number connected to one workspace cannot be connected to a second — the attempt is refused with *"This WhatsApp number is already connected to another workspace."* - A phone number provisioned for a workspace answers only that workspace's deployments. - An API key reads and writes only its own workspace's data. There is no key that spans two. If your company needs a hard separation — production versus a partner brand, or two subsidiaries that must not see each other's customers — two workspaces is the mechanism. There is no lighter-weight partition inside one. ## The billing boundary The plan, the seat count, and the usage meters are all properties of the workspace, not of a person. Your plan sets hard caps on how many agents, knowledge bases, deployments, and team seats you can create; creating past a cap is refused with a message naming the limit. [Limits and plans](/operate/limits) has the numbers. Two consequences worth knowing before they surprise you: - **Pending invites consume seats.** The Members tab counts active members plus outstanding invitations against the seat limit, so an invite that was never accepted still blocks the next one. Revoke it. - **A suspended workspace stops working, for everyone.** If payment goes unpaid for long enough the console collapses to the billing page for every member, and voice calls are answered with an out-of-service line rather than by your agent. Only an owner can clear it. ## The three roles There are exactly three: `owner`, `admin`, and `member`. They are ranked, and every permission check is "this rank or above" — so an owner passes every admin check without needing a second role. There is exactly one owner per workspace, created when the workspace is created. Ownership moves by transfer, not by promotion. | | Member | Admin | Owner | |---|---|---|---| | Read the inbox, tickets, calls, analytics | Yes | Yes | Yes | | Work tickets they have claimed | Yes | Yes | Yes | | Read agents, knowledge, and the retrieval tester | Yes | Yes | Yes | | Test an agent | Yes | Yes | Yes | | Add, edit, or delete knowledge sources | — | Yes | Yes | | Create, publish, or archive an agent | — | Yes | Yes | | Deploy, pause, or remove a deployment | — | Yes | Yes | | Connect channels, request phone numbers, submit compliance | — | Yes | Yes | | Create, roll, and revoke API keys and webhooks | — | Yes | Yes | | Add, edit, import, or delete contacts | — | Yes | Yes | | Assign tickets to other people, acknowledge escalations, set routing | — | Yes | Yes | | Edit organisation details | — | Yes | Yes | | Invite, remove, and re-role teammates | — | Yes | Yes | | Change the plan, pay, or cancel the subscription | — | — | Yes | | Transfer ownership | — | — | Yes | ### What a member actually experiences Member is a genuine working role, not a read-only spectator seat. A member can handle the whole support day: read every conversation in the inbox, claim an unassigned ticket, reply on it, add internal notes, escalate something they cannot resolve, and use the AI copilot on tickets they have claimed. What they cannot do is change how the product behaves. The write controls on Knowledge are removed from their view rather than shown as disabled buttons, and Channels and Developers are not in their sidebar at all. Three member-specific rules in ticketing are worth stating because they surprise people: - A member must **claim** a ticket before working it. The composer says `Claim this ticket before working on it.` until they do. - A member cannot assign a ticket to someone else, only to themselves. - A member sees active tickets. Resolved and closed queues are an admin view. ### What admin adds Admin is the "configures the product" role. Everything that changes what customers experience — the agent's instructions, what it knows, what it can do, which number it answers on — is admin and above, as is everything that hands out credentials. Two admin powers deserve deliberate thought before you grant them: - **API keys.** An admin can mint a key that sends WhatsApp messages and places calls on behalf of the whole workspace. The console says so on the page: *"API keys can send messages and place calls on behalf of this whole workspace, so only owners and admins can manage them."* - **Deployments.** An admin can point a live phone number at a different agent. The change is auditable and reversible, but it takes effect on the next call. ### What owner adds Owner adds money and succession: changing plan, paying, cancelling, and transferring ownership. That is the entire delta. An owner is not a "super admin" with extra product powers — an admin can already configure everything. Transferring ownership is a swap, not a copy: `Transfer ownership to {name}? You'll become an admin.` The workspace never has two owners and never has none. ## Inviting teammates Invitations live in **Settings → Members**. ::: steps ::: step Send the invite Enter the teammate's email and pick a role. The role select offers **Member** and **Admin** only — you cannot invite someone as an owner, because ownership transfers rather than being granted. The invite becomes a link. Share it however you normally share links; the invitation is also recorded under **Pending invites** with its role and expiry. ::: ::: step Watch the seat count The card reads `{used} of {limit} plan seats used (pending invites count)`. If you are at the cap the invite is refused with `seat_limit_reached` and a message naming your plan's seat allowance. Revoking a stale pending invite frees its seat immediately. ::: ::: step Change or remove later Each member row carries a role select (`Member` / `Admin`), a transfer-ownership control, and a remove control. Changing someone from admin to member takes effect on their next request; it does not revoke API keys they created, because keys belong to the workspace rather than to them. ::: ::: ::: warning Removing a person does not revoke their keys An API key is a workspace credential. If the person who created it leaves, the key keeps working until an admin revokes or rolls it. Off-boarding should include a pass over **Developers → API keys**. ::: ## API keys are workspace credentials This is the single most important thing to understand about programmatic access, and it is a deliberate design choice rather than a missing feature. An API key authenticates **the workspace**, not a user and not an agent. There is no per-user key, no per-agent key, and no per-origin key. A key does not inherit the role of whoever created it, and actions taken with it are not attributed to a person. ```mock {"component": "ApiKeysScreen", "props": {"caption": "Keys are named for where they run, not for who made them. The scope column is the only thing narrowing what each one can do."}} ``` **Scopes are the only partition.** A key carries a set of the six scopes, granted at creation, and that set is the whole of its authority: | Scope | Lets the key | |---|---| | `messages:send` | Send WhatsApp messages and templates | | `calls:write` | Place a call, read its outcome, hang it up | | `conversations:read` | List conversations and read transcripts | | `contacts:write` | Create or match a contact | | `tickets:write` | Open a ticket, and read one back | | `agents:read` | List the agents in the workspace | A newly created key defaults to `conversations:read` and `agents:read` — read-only, on the principle that a key is easier to widen later than to un-leak. [Scopes](/api/scopes) has the full endpoint matrix, including the fact that reading a ticket requires `tickets:write` because there is no read-only ticket scope. Practical consequences of workspace-scoping: - **Give each system its own key.** Not because the keys differ in power by default, but because separate keys are separately revocable and separately visible in the request log. Your order service and your CRM sync should not share one. - **Grant the narrowest scope set that works.** A key that only needs to send order updates gets `messages:send` and nothing else. It then cannot read a single conversation even if it is stolen. - **Never put a key in front-end code.** `/api/v1` sends no CORS headers precisely so that this fails loudly rather than working until someone reads your bundle. - **Rotate with Roll, not Revoke.** Rolling issues a replacement and keeps the old key working for 24 hours, which is enough to redeploy. Revoking kills it within seconds. The full mechanics — the one-time reveal, the `amk_live_` / `amk_test_` split, and the six distinct causes of a 401 — are in [Authentication](/api/authentication). ## Who should be what Some opinionated defaults that hold up in most teams. **Owner: one person, usually whoever owns the budget.** Not the most technical person, and not a shared account. The owner is who your renewal fails on and who has to act at 9 a.m. on a Monday when a payment bounces. Transfer it when that person changes jobs rather than sharing their login. **Admin: the two or three people who build the product.** Whoever writes the agent's instructions, curates the knowledge, and owns the integration. Keep this list short — an admin can repoint a live phone number and mint a key that sends messages to your entire customer list. **Member: everyone else, including most of the support team.** A member can do all the day-to-day work and cannot accidentally change what an agent says to twenty thousand customers. Support agents, analysts, and anyone who mainly reads should be members. Two failure modes to avoid: - **Making everyone an admin so nobody is blocked.** The most common reason a live agent changed unexpectedly is that seven people could change it. - **Making the only technical person a member.** They will be blocked on every knowledge fix and you will end up sharing an admin login, which is worse than the grant you were avoiding. ## Also on the Settings page Beyond Members, the settings tabs cover a few workspace-level things worth knowing where to find: - **Organization** — the workspace name, website, and the **Organization ID** you should quote when you contact support. - **Compliance** — the one-time identity verification required before phone numbers can be issued to the workspace. See [Numbers and KYC](/channels/phone-numbers). - **Profile** — your own display name, and phone verification so the platform can ring you when you call a customer from a ticket. - **Notifications** — which events reach you in-app and by email. Security and billing emails are always sent and cannot be turned off. ## Next ::: cards ::: card How it fits together [The model in one page](/concepts/architecture) — agents, versions, deployments, and channels. ::: ::: card Authentication [How keys work](/api/authentication) — format, one-time reveal, rolling, and every cause of a 401. ::: ::: card Scopes [What a key can do](/api/scopes) — six scopes and the endpoint matrix. ::: ::: card Security and data [Where your data lives](/operate/security) and how it is protected. ::: ::: ---------------------------------------------------------------------------- ## Conversations & contacts URL: https://docs.amomic.in/concepts/conversations ---------------------------------------------------------------------------- Every channel converges here. A widget chat at 11 p.m., a WhatsApp thread, a phone call — all three become a **conversation** with a full transcript, and all three resolve to a **contact** in the same customer directory. This page explains both, including where the identity story is still incomplete. ## What a conversation is A conversation is one continuous exchange between a customer and an agent on one channel. It carries: | Field | What it is | |---|---| | `conversation_id` | The stable identifier. Use it to fetch the transcript. | | `channel` | Where it happened — `web`, `whatsapp`, or `voice`. | | `agent_id` | Which agent handled it. | | `status` | Where it stands; open conversations report `open`. | | `customer` | What is known about the person — name, email, and the channel-specific identifier. | | `message_count` | How many messages it contains. | | `preview` | The first 140 characters of the last real message, for list views. | | `created_at` / `updated_at` | When it started and when it last moved. | Conversations are per channel by design. A customer who chats on your website in the morning and messages your WhatsApp number in the afternoon has two conversations — because they are two different threads, with different histories and different constraints. They resolve to **one contact**, which is where the two halves come back together. A voice call produces a conversation like any other. The transcript is the turns of the call, speaker by speaker; the audio recording, the call outcome, and the response-latency figures live alongside it on the call record. See [How voice works](/channels/voice). ## Message roles The public transcript has exactly two roles. | Role | Who | |---|---| | `user` | The customer. What they typed, or what they said on the call. | | `model` | The agent. | That is the whole vocabulary. There is no `system` role in a transcript, no `assistant` alias, and no tool-call messages. ### The operational trail Alongside those messages, the platform records an internal trail of what the agent *did* between turns: which knowledge base it searched, which live API it called, which MCP tool it used, which action it took, and — on voice — why it played a filler line while it was waiting. **That trail is deliberately excluded from the public transcript.** [`GET /conversations/{id}/messages`](/api/conversations) returns only `user` and `model` messages, so a transcript you sync into your warehouse or show to a customer contains the conversation and nothing else. In the console inbox the trail *is* shown, rendered as thin rows rather than message bubbles, labelled by what happened: `Knowledge base`, `Live API`, `MCP tool`, `Action`. On calls you will also see why the agent spoke while thinking — `while looking something up`, `covering a slow reply`, `the wait ran long`, `checking the caller was still there`, `ending the call after silence`, `call time limit reached`. ::: tip Read the trail first when an answer was wrong It separates three bugs that look identical from the outside. No lookup at all means the agent answered from its own head — a knowledge gap. A lookup that returned the wrong passages means a retrieval problem. The right passages plus a wrong answer means a persona problem. See [Testing and publishing](/build/testing). ::: ## The inbox Every conversation from every channel lands in one inbox with its full transcript, tagged with the channel it came from. You do not check three places. ```mock {"component": "InboxScreen", "props": {"caption": "Every conversation from every channel, with the full transcript and the operational trail."}} ``` The list is searchable and filterable by agent. Each row shows the agent, the customer's channel identifier, a relative timestamp, and either the last message or a message count; a `Preview` badge marks conversations from the console's own widget preview rather than a real visitor. The transcript pane shows the channel badge, when the conversation started, how many messages it holds, and the messages themselves. Images sent by a customer render inline. ::: warning The inbox is read-only There is no takeover control and no reply box. You cannot join a live conversation, send a message as the agent, or intervene mid-chat from the inbox. It is a complete record of what happened, not a shared console. When a customer needs a person, the route is a **ticket** — the agent opens one with `create_support_ticket`, your team works it in [Tickets](/api/tickets), and the ticket carries the conversation reference so whoever picks it up has the history. That is the supported hand-off path. ::: ## Reading conversations from your systems Two endpoints, both requiring the `conversations:read` scope: - [`GET /conversations`](/api/conversations) — the list, newest activity first, 25 per page by default and up to 100. Filter by `agent_id`. Walk pages with `next_cursor` until it comes back `null`. - [`GET /conversations/{id}/messages`](/api/conversations) — the transcript, **oldest first**, 100 per page by default and up to 200. An unknown or foreign conversation id returns 404 rather than an empty page, so a bad id can never look like a quiet conversation. Internal fields are not projected — you will not see workspace ids, deployment ids, connection ids, or per-message metadata. What comes back is the conversation and its transcript. For a scheduled sync into a warehouse, see [Sync conversations](/recipes/sync-conversations) and [Pagination](/api/pagination). ## Contacts A contact is a person in your customer directory. It holds: - `contact_id`, and the workspace it belongs to - `name`, `phone`, `email`, and `wa_id` (their WhatsApp identifier) - `tags` - `consent` — separate permissions for `voice`, `whatsapp`, and `email` - `last_seen_channel` - `created_at` Phone numbers are normalised on the way in. A bare ten-digit Indian number becomes `+91…` automatically; anything that does not land between 8 and 15 digits is rejected with `Phone number must contain 8 to 15 digits.` Email addresses are trimmed and lower-cased, so `Priya@Example.com` and `priya@example.com` are the same person. WhatsApp identifiers are reduced to digits. ## How a person is identified There are exactly three match keys: **email**, **phone**, and **WhatsApp ID**. Every lookup is scoped to your workspace before anything else happens. The resolution logic, in order: ::: steps ::: step Look up each identity separately Whatever identities are known — an email, a phone number, a WhatsApp ID — each is queried against the directory on its own. ::: ::: step If exactly one contact matches, enrich it The existing contact is updated with **only the fields that were empty**, plus `last_seen_channel`. A supplied value never overwrites a value that is already there. ::: ::: step If a supplied identity conflicts, stop If the matched contact already has a different email — or a different phone — the operation fails with `The contact already has a different {field}; manual review is required.` rather than silently choosing one. ::: ::: step If the identities match two different contacts, stop `The supplied identities belong to different contacts; manual review is required.` A phone number belonging to Rohit and an email belonging to Priya, arriving in the same request, is a data problem a human should look at — not something to resolve by guessing. ::: ::: step Otherwise, create A new contact is created, tagged `ai-captured` when the agent captured it. ::: ::: ::: note There is no merge Nothing in the platform merges two contacts into one. That is a deliberate refusal, not a missing feature — an automatic merge of two records that a conflict check already flagged as suspicious is how one customer's phone number ends up on another customer's file. Conflicts surface for a human to resolve. ::: Over the public API, [`POST /contacts`](/api/contacts) upserts a contact and matches **by email first, then phone**. It requires at least one of the two — a name-only request is rejected with `Provide an email or a phone number.` If the details belong to two different existing contacts you get a 409 with `Those details belong to two different contacts.` ## How a contact accumulates across channels This is the payoff, and it is worth being precise about how it happens. Priya messages your WhatsApp number about a delayed order. The agent resolves her by her WhatsApp identifier, finds nothing, and creates a contact carrying her `wa_id` and the phone number derived from it. Later in the chat she gives an email so she can be updated — that email lands on the same contact, because it was empty. Three weeks later she opens your website and fills in the widget's lead form with that same email. The resolver matches the email to the existing contact and enriches it rather than creating a second one. Her name from the web form fills a field the WhatsApp conversation never captured. She then calls your support number. The agent resolves her by caller ID, which matches the phone number already on the record. One contact. Three channels. No manual reconciliation — and, importantly, no guessing: each step matched on an identifier the channel itself supplied, not on a name that happened to look similar. ### Where contacts come from | Source | Behaviour | |---|---| | Web widget lead form | Creates or enriches on submit. If it fails, the visitor never sees an error — the chat continues. | | Agent opening a ticket on web chat | Asks first name, then email; asks for a phone number only when creating a new contact. | | Agent opening a ticket on WhatsApp | Uses the chat's own number; asks for an email only if the contact has none. | | Agent opening a ticket on a call | Uses the caller's number only. Email is never captured on a call — speech recognition is not reliable enough to spell an address. | | [`POST /contacts`](/api/contacts) | Upsert from your own systems. Matches by email, then phone. | | Console, and CSV import | Manual entry, or a CSV with `name`, `phone`, and `email` columns. | ::: warning Two honest gaps **A conversation alone does not create a contact.** Somebody has to give an identity — through the lead form, through a ticket, or through the API. A customer who chats anonymously and never identifies themselves leaves a conversation and no directory entry. The console says so plainly on the contacts page. **CSV import does not deduplicate.** Rows are created directly, without the resolution logic above. Importing a list that overlaps your existing directory produces duplicates, and there is no merge to clean them up afterwards. Import into a fresh directory, or check for overlap first. ::: A contact's **activity timeline is currently empty**. It shows an honest empty state rather than fabricated history: automatic linking of calls and conversations onto a contact record is not enabled yet. To see a customer's history today, search the inbox or their tickets. ## What converges, and what does not **Converges:** every conversation from every channel into one inbox; every identified customer into one contact record; every channel's volume into one set of analytics. **Does not converge:** conversations themselves. A WhatsApp thread and a web chat with the same person remain two conversations, linked only by the contact. There is no unified per-customer thread view. ## Where to go next ::: cards ::: card Read transcripts from code [Conversations API](/api/conversations) — list, fetch, and page through messages. ::: ::: card Upsert a customer [Contacts API](/api/contacts) — matching rules, conflicts, and what comes back. ::: ::: card Hand off to a human [Escalate to a ticket](/recipes/support-escalation) — the supported path when the agent cannot finish the job. ::: ::: card Get told instead of polling [Webhooks](/webhooks/overview) — `conversation.started`, `conversation.ended`, and the rest. ::: ::: ---------------------------------------------------------------------------- ## Glossary URL: https://docs.amomic.in/concepts/glossary ---------------------------------------------------------------------------- Every term you will hit in this documentation, alphabetised, with a link to the page that covers it in full. If a word here is doing more work than you expected, follow the link — most of these are small ideas with a sharp edge somewhere. ### 24-hour window WhatsApp's rule, not ours: you may send free-form messages to a customer only within 24 hours of their last message to you. Outside that window, only pre-approved templates go out, and a free-form attempt is refused with a `409` and `session_window_closed`. The clock resets every time the customer messages you again. No template is sent automatically when a window closes — if something needed to go out, it does not go out later. → [The 24-hour window](/channels/whatsapp-window) ### Agent The thing your customer talks to: a persona, the knowledge bases attached to it, and the tools it may call, bundled together. An agent is channel-neutral — you write it once and each channel renders it appropriately, so the same answer arrives as light markdown in the web widget, as WhatsApp's own formatting on WhatsApp, and as spoken prose on a call. An agent cannot be reached by customers until it is published as a version and that version is deployed. → [Agents and versions](/build/agents) ### API key The credential your backend uses to call the public API, formatted `amk_live_…` or `amk_test_…`. It is scoped to a whole workspace and to a specific set of scopes, sent as `Authorization: Bearer …` or `X-Api-Key`, and only ever stored as a hash — the plaintext is shown once, at creation, and never again. Because it is workspace-scoped, a key in front-end JavaScript is a leaked workspace; the API is server-side only and sends no CORS headers. → [Authentication](/api/authentication) ### Call mode A voice deployment's setting for what a caller may speak, labelled **Call language** in the console. Three values: `en` (English only, highest transcription accuracy), `multi` (Hindi and English, handling callers who switch mid-sentence), and `indic` (ten Indian languages, following the caller as they switch). It also gates which languages the call may open in — you cannot greet in Tamil on a number set to `en`. → [Languages](/channels/voice-languages) ### Channel A kind of touchpoint where customers reach you: web chat, WhatsApp, or voice. Distinct from a **channel connection**, which is a specific one you own and have verified — this website's domain, that WhatsApp Business number, this phone number. Connections are created and verified in the console; the API does not create them. → [How it fits together](/concepts/architecture) ### Chunk A passage of your content, indexed on its own. Ingested sources are split into passages of roughly 1,600 characters on paragraph boundaries, each carrying the last 200 characters of the previous one so a fact straddling a boundary is not lost. Retrieval works at this granularity: the agent is given passages, never whole documents. The console reports source sizes in chunks, which is a useful proxy for how much an agent actually knows. → [Knowledge bases](/build/knowledge) ### Contact A person in your customer directory, holding a name, phone, email, WhatsApp identifier, tags, and per-channel consent. Contacts are matched on email, phone, and WhatsApp ID, and an existing contact is enriched only in its empty fields — a supplied value never overwrites one that is already there. There is no merge: conflicting identities stop the operation for a human to resolve. → [Conversations and contacts](/concepts/conversations) ### Conversation One continuous exchange between a customer and an agent on one channel, with a full transcript. Conversations are per channel by design, so the same person messaging you on WhatsApp and chatting on your website has two conversations linked by one contact. The public transcript contains only `user` and `model` messages; the internal record of what the agent looked up or called is kept separately and shown only in the console inbox. → [Conversations and contacts](/concepts/conversations) ### Deployment An agent version, on a channel connection, with channel-specific configuration — the only object that decides what a real phone number, WhatsApp sender, or website widget actually does. It moves through `draft` → `ready` → `active` → `superseded` or `archived`. Editing an active deployment is refused; the supported path clones it, validates the clone, and activates it, so a bad edit can never take down a working number. → [Deployments](/build/deployments) ### DID Direct Inward Dialling number — the phone number customers actually dial, provisioned to your workspace. A DID is inert on its own: it rejects calls until an agent is explicitly deployed to it, and a workspace-wide voice setting will not substitute for that. It also serves as the caller ID for outbound calls, and the API will only place a call from a DID your workspace owns and has active. → [Numbers and KYC](/channels/phone-numbers) ### Idempotency key A value you send as the `Idempotency-Key` header on a request that spends money, so a retry cannot double-send. Replaying the same key with the same body returns the original stored response without touching the provider; replaying it with a *different* body is a `409` with `idempotency_key_reuse`. Keys are scoped to your workspace, the specific API key, and the endpoint, and they expire after 24 hours. → [Idempotency](/api/idempotency) ### Knowledge base A container of sources that an agent can be taught from. Creating one does not make any agent smarter — an agent only sees knowledge explicitly attached to it, and the same knowledge base can teach several agents at once. Deleting a knowledge base stops its content serving every connected agent immediately, without a republish. → [Knowledge bases](/build/knowledge) ### Opening language The language a phone call opens in, set per voice deployment as **Greet callers in**. It is not the same as the call mode: the mode decides what the caller may speak, the opening language decides only the first line. The agent follows the caller from their first reply regardless, so an opening language is a starting register, not a constraint on the conversation. → [Languages](/channels/voice-languages) ### Public reference The customer-safe identifier for a ticket, formatted `AM-` followed by eight uppercase hex characters — `AM-4F91C2E0`. It is what an agent reads back to a customer and what your team quotes in a reply, and it is derived from the ticket rather than being a sequential counter, so it reveals nothing about your volume. Every ticket has one, whether the agent created it or your backend did. → [Tickets](/api/tickets) ### Retrieval The process of finding the passages most relevant to a customer's question and putting them in front of the model before it answers. It works on meaning rather than keywords, so a question and its answer need not share any words — and, equally, no amount of exact phrasing will find a passage that was never indexed. One search runs per attached knowledge base and the results are merged closest-first. → [Knowledge bases](/build/knowledge) ### Scope A capability granted to an API key, named the way a customer would describe it out loud rather than after an HTTP route. There are exactly six: `messages:send`, `calls:write`, `conversations:read`, `contacts:write`, `tickets:write`, and `agents:read`. New keys are created read-only by default, on the principle that a key is easier to widen later than to un-leak. → [Scopes](/api/scopes) ### Sender The WhatsApp Business number that messages go out from, and that customers reply to. A workspace can have more than one, and an agent can be pinned to a specific sender or use the workspace default. The sender is also what the 24-hour window is tracked against, so a customer's window with one of your numbers says nothing about their window with another. → [Set up WhatsApp](/channels/whatsapp) ### Source One item of content or connectivity inside a knowledge base. There are exactly five types: `website` (crawled), `file` (uploaded documents and images), `text` (pasted), `endpoint` (your HTTP API, either ingested on a schedule or called live), and `mcp` (an MCP server whose tools become abilities). Each source reports its own status and, where applicable, its own refresh schedule. → [Knowledge bases](/build/knowledge) ### Template A WhatsApp message whose wording Meta has approved in advance, with placeholders for the values you fill in at send time. Templates are the only thing you can send outside the 24-hour window, which makes having at least one approved utility template the difference between reaching a customer and not. Categories that work end to end are `UTILITY` and `MARKETING`; an agent can only send templates you have explicitly added to its allowlist. → [Templates](/channels/whatsapp-templates) ### Ticket A unit of support work, created by an agent when it cannot finish the job, by your team in the console, or by your backend over the API. It carries a status, a priority, an assignee, an SLA clock set at creation, the originating conversation, and a public reference. Tickets are the supported path from AI to human — the inbox itself is read-only, so escalation is what a hand-off means here. → [Tickets](/api/tickets) ### Tool Something the agent can *do* rather than know. Four are built in — `create_support_ticket`, `send_whatsapp_message`, `search_knowledge`, and `end_call` — and your own tools are built as `endpoint` or `mcp` sources on a knowledge base, each declaring whether it reads or acts. A disabled tool is not declared to the model at all, so it cannot be attempted and refused; it does not exist. → [Tools and actions](/build/tools) ### Version An immutable snapshot of an agent: its persona, its knowledge bindings, and its tool policy, frozen together with a sequence number and a configuration hash. There is no edit path for a published version, which is exactly what makes "roll back to version 3" mean something — version 3 is still the agent that worked, including which knowledge it was allowed to see. Publishing creates a version; it does not deploy it. → [Agents and versions](/build/agents) ### Webhook An HTTPS endpoint of yours that we POST to when something happens, so your systems learn about events without polling. Nine event types exist, covering messages, conversations, calls, tickets, and leads. Every delivery is signed, and you must verify the signature against the raw request body — parsing the JSON and re-serialising it changes the bytes and the signature will not match. → [Webhooks](/webhooks/overview) ### Workspace Your organisation, and the isolation boundary for everything else. It owns the agents, the knowledge, the channel connections, the conversations, the contacts, the API keys, and the bill. Every query is filtered by workspace before anything else happens, and there is no mechanism — no sharing setting, no cross-workspace agent, no shared knowledge base — for data to cross it. → [Workspaces and roles](/concepts/workspace) ============================================================================ # SECTION: Build an agent ============================================================================ ---------------------------------------------------------------------------- ## Agents & versions URL: https://docs.amomic.in/build/agents ---------------------------------------------------------------------------- An agent is the thing your customer talks to. You write it once — a name, a purpose, some instructions, the knowledge it may use, the tools it may call — and every channel renders that same agent appropriately. This page covers the fields, what each one actually does to behaviour, and the publish-and-version mechanism that makes it safe to edit an agent that is currently live. If you have not read [How it fits together](/concepts/architecture), read it first. It explains why an agent, a version, and a deployment are three different things; this page assumes you know. ```mock {"component": "AgentsScreen", "props": {"caption": "Agents you have built. Each card shows its publication state, its channels, and how much of its knowledge is ready."}} ``` ## Creating an agent **Agents → New agent.** Four fields, and only two are required. | Field | Required | Limit | What it does | |---|---|---|---| | **Agent name** | Yes | 60 characters | What your team calls it, and what a customer sees on the web widget and hears on a call. | | **Purpose** | Yes | 500 characters | The outcome this agent owns. This becomes the spine of its behaviour. | | **Additional instructions** | No | 4,000 characters | Style, tone, and specific rules that are not a policy fact. | | **Business knowledge** | Yes (defaulted) | — | Whether a ready knowledge base is required before this agent may be published. | The agent is created as an unpublished draft. Nothing is provisioned, no channel is touched, no customer can reach it. You can create it, forget about it for a week, and come back. ::: note The name is customer-facing on some channels On a phone call the outbound opening line uses the agent name — "this is Asha, a virtual assistant calling on behalf of…" — so `Support Agent v2 (test)` is a name your callers will hear. Use something you would say out loud. ::: ### Business knowledge: `required` vs `no_business_knowledge` This is a two-option select, and it is the single setting most likely to prevent an embarrassing launch. | Option in the console | Stored value | Effect | |---|---|---| | Require a ready knowledge base before publishing | `required` | The agent's readiness check fails until at least one attached knowledge base is serving content. You cannot publish an empty agent by accident. | | No business knowledge — safe general behavior only | `no_business_knowledge` | You are stating on the record that this agent answers with no company-specific content. Readiness stops asking for knowledge. | Leave it on `required` unless you are deliberately building something that has nothing to look up — a pure call-router, or a bot whose only job is to open a ticket. The moment an agent is expected to answer a question about *your* business, `required` is the setting that stops it going live with nothing to stand on. ## Writing a purpose that produces a good agent The purpose field is 500 characters and it does more work than anything else you will type. It is not a description for your colleagues. It is the sentence the agent uses to decide, on every single turn, whether a question is its job. A vague purpose does not produce a vague agent. It produces a *confident* agent with no boundaries — one that answers billing questions it has no information about, because nothing told it that billing was not its job. ### The shape that works A good purpose answers three questions in order: 1. **What does it resolve?** The concrete outcomes, named. 2. **From what?** The authority it is allowed to draw on. 3. **What does it hand off?** The line it does not cross. That is it. Three clauses, usually under 300 characters. ### Before and after **Support agent — before:** > Help customers. Two words, and every one of the three questions is unanswered. This agent will attempt anything anyone asks it, including questions about pricing tiers it has never seen and complaints it cannot resolve. It has no reason to hand anything off, so it will not. **Support agent — after:** > Answer delivery, refund, and order-status questions from our published policy; hand off anything about billing. Now the agent knows three topics are in scope, that the published policy is the authority, and that billing is somebody else's. When a customer asks about a failed payment, it opens a ticket instead of improvising a refund rule. --- **Receptionist — before:** > Answer the phone for our clinic. This describes the channel, not the job. "Answer the phone" is what the deployment does. The agent still does not know what a successful call looks like. **Receptionist — after:** > Tell callers our timings, location, and which doctors are available on which days, from the clinic information sheet. Take a callback request for anything about test results or fees. Note what changed: the outcomes are named, the source is named, and there is an explicit escape hatch that produces a concrete action rather than an apology. --- **Order desk — before:** > Handle customer queries about orders and be helpful and friendly. Our company was founded in 2019 and we sell home appliances across India with a focus on customer satisfaction. This is the most common failure and it looks like effort. Half the field is company background — which belongs in a knowledge base, where it can be searched and updated — and the operative half is still "handle queries". Worse, facts written into the purpose are frozen into the published version, so when they change you must republish the agent rather than re-crawl a page. **Order desk — after:** > Resolve order-status, delivery-date, and return-eligibility questions using the order lookup tool and our returns policy. Confirm the order number before quoting anything. Escalate damaged-on-arrival cases to a ticket. ### Rules of thumb - **Name the outcomes, not the audience.** "Support our B2B customers" is not an instruction. "Answer contract, invoice, and onboarding questions" is. - **Describe the job, not the channel.** The same agent runs on the widget, WhatsApp, and the phone. A purpose that says "reply to WhatsApp messages" is wrong on two of the three. - **Always include the hand-off clause.** An agent with no stated boundary treats every question as in scope. One sentence — "hand off anything about X" — is what makes it reach for [`create_support_ticket`](/build/tools) instead of guessing. - **Do not put facts in the purpose.** Prices, timings, and policies belong in a [knowledge base](/build/knowledge). Facts in the purpose can only be changed by publishing a new version; facts in a knowledge source can be re-crawled without touching what is live. - **Read it back as an instruction to a new hire on day one.** If a competent human could not act on it, neither can the agent. ## Additional instructions Instructions are for *how*, once the purpose has settled *what*. Four thousand characters is a lot of room, and most of it should stay empty. Good instructions look like: ```text Be concise — two or three sentences unless the customer asks for detail. Ask one clarifying question at a time, never a list. Quote the refund window exactly as written in the policy; never round it. If a customer is angry, acknowledge it once and move to the fix. Never invent an order number, a date, or a policy detail. ``` Bad instructions look like a second purpose, or a knowledge base in disguise ("Our office hours are 9 to 6…"). If a line contains a fact that could change, it is in the wrong place. ::: danger Never put credentials in instructions The console says this next to the field and it is worth repeating: no API keys, no access tokens, no customer secrets, no internal URLs with a token in the query string. Instructions are copied verbatim into every published version and are visible to anyone in the workspace who can open the agent. Credentials for a tool belong in the source's headers, where they are moved into encrypted storage and never shown again — see [Tools and actions](/build/tools). ::: ## Persona settings The **Persona** tab is where the agent lives after creation. It holds everything from the create form plus three more controls. | Field | Options | Notes | |---|---|---| | **Agent name** | 60 characters | Same field as on create. | | **Language** | `English` (`en`), `Hindi` (`hi`), `Multilingual` (`multi`) | The register the agent writes and speaks in. `multi` lets it follow the customer. | | **Purpose** | 500 characters | | | **Instructions** | 4,000 characters | | | **Grounding policy** | `Strictly grounded` (`grounded`), `Balanced` (`balanced`) | How tightly it must stay inside your content. | | **Knowledge requirement** | `required`, `no_business_knowledge` | Same setting as **Business knowledge** on create. | Everything on this tab is channel-neutral. There is no per-channel persona — you cannot give WhatsApp one tone and voice another. If two channels genuinely need different behaviour, build two agents. ### Grounding policy `grounded` is the default and it is the right default. - **`grounded`** — the agent answers from retrieved passages and says it does not know when the content does not cover the question. It will still be conversational; it will not fill a gap with something plausible. - **`balanced`** — the agent leans on your content but is permitted more general reasoning around it. Choose `balanced` only when you have watched a `grounded` agent in the test tab refuse things you were happy for it to answer. Turning it up because answers feel "stiff" usually means the knowledge base is thin, and the fix is a source, not a policy. ### Language versus voice language The persona **Language** field is not the same thing as a voice deployment's call language. | Setting | Where it lives | What it controls | |---|---|---| | Persona **Language** | The agent, frozen into every version | The register the agent thinks and writes in across all channels | | **Call language** and **Greet callers in** | The voice deployment, per phone number | What a caller may speak, and which language the call opens in | A number can open in Hindi while the agent's persona language is `multi`. See [Languages](/channels/voice-languages) for the full matrix. ## What publishing does While you are editing, you are editing a draft. Nothing you type reaches a customer. Choosing **Publish** captures the current state of the agent as a **version**, and the console is precise about what "current state" means: - the **persona** — name, language, purpose, instructions, grounding policy, knowledge requirement - the **knowledge bindings** — which knowledge bases are attached, and which revision of each - the **tool policy** — which tools are enabled, which WhatsApp templates are allowed, whether action tools may fire Those three are snapshotted together, stamped with a sequence number and a configuration hash, and written once. **There is no update path for a published version** — not in the console, not in the API, not for support. The only way to change anything is to publish another one. Your next keystroke starts a fresh draft. The agent's badge moves to `Changes to publish` and the version that is actually running does not move. ### Why immutability is the point Three things become true only because versions cannot be edited. **You can work on a live agent in the middle of the day.** Rewrite the instructions, re-crawl the website, attach a new knowledge base, run twenty test conversations — the version deployed on your phone number keeps answering exactly as it did this morning. Nothing in the studio reaches a customer until you publish *and* deploy, both on purpose. **"Roll back to version 3" means something.** If versions could be edited, rolling back would mean returning to whatever version 3 has become since. Because they cannot, version 3 is still exactly the agent that worked — including which knowledge it was permitted to see. **An incident has a fixed subject.** When a customer reports a bad answer from last Tuesday, the version that produced it still exists, unchanged, with its config hash. You can read it instead of reconstructing it. ::: warning Publishing is not deploying Publishing creates a version. It does not put that version anywhere. Until you deploy it to a channel connection, a freshly published agent shows `Published, not deployed yet` and no customer can reach it. See [Deployments](/build/deployments). ::: ## Publication badges The agents list shows one of three strings per agent. They are worth learning because they answer "is my change live?" without opening anything. | Badge | Meaning | |---|---| | `Not published` | The agent has never been published. It has no version, so it cannot be deployed. | | `Published` | Every change you have made is captured in the current version. The draft is clean. | | `Changes to publish` | There is a published version *and* a newer draft. What is live is the older one. | Two more strings appear where deployment matters: `Published, not deployed yet` (a version exists but nothing is serving it) and `Not deployed yet`. Inside the agent studio the same three states read as `Published`, `Unpublished changes`, and `Not published`, and the **Overview** tab shows the underlying draft state directly — `clean` or `unpublished changes`. ::: tip `Changes to publish` is not a warning It is the normal, healthy state of an agent someone is improving. It becomes a problem only when you believed you had shipped something. If a fix is not visible to customers, this badge is the first place to look. ::: ## Version history and rollback The **Versions** tab lists every published version, newest first. Each row carries: - `Version {n}` and, on one of them, a `Current` badge - the change summary you wrote, or `Published configuration` when you wrote none - how many knowledge bindings the version froze - when it was published, and a truncated configuration hash The configuration hash is the fastest way to answer "did anything actually change?" Two versions with the same hash are the same agent. ::: tip Write the change summary It costs five seconds and it is the only human-readable record of why a version exists. `Added the 2026 returns policy and stopped it quoting delivery estimates` is worth far more in three months than `Published configuration`. ::: ### Rolling back Rollback happens at the deployment layer, not the agent layer. A deployment names one specific agent version, so restoring old behaviour means putting the old version back on the connection: ::: steps ::: step Find the deployment that is serving the bad behaviour Open the agent's **Deployments** tab and identify the channel and connection — the phone number, the WhatsApp sender, or the site. ::: ::: step Roll it back to the previous deployment The previous deployment on that connection is still there, marked `Replaced`. Rolling back re-activates its configuration and its agent version in a single transaction, superseding the bad one. Your draft is untouched. ::: ::: step Fix the draft, then publish forward Rollback buys you time; it is not a fix. The draft still contains whatever caused the problem. Correct it, test it, publish a new version, and deploy that. ::: ::: [Deployments](/build/deployments) covers activation, supersession, and the clone rule in full. ## Archiving an agent Archiving removes an agent from daily work without deleting anything. Conversations, calls, and published version history are all kept, and you can restore it from the **Archived agents** section at any time. **Archiving is blocked while anything is live.** If the agent has active deployments, the console tells you how many and refuses until you pause them from the **Deployments** tab. That is deliberate — archiving is a tidying action, and tidying should never silently drop a customer's call. ## How many agents you can have Agent count is capped by your plan, and there is a hard workspace backstop of 20 agents regardless of plan. Most workspaces need far fewer than they expect: one agent serving four channels is the normal shape, not four agents. Build a second agent when the *job* differs — a sales qualifier and a support resolver have different purposes, different knowledge, and different hand-off rules. Do not build a second agent because you have a second channel. See [Limits and plans](/operate/limits). ## Where to go next ::: cards ::: card Give it something to know [Knowledge bases](/build/knowledge) — the five source types, how retrieval actually works, and how to get good answers out of it. ::: ::: card Let it do things [Tools and actions](/build/tools) — the built-in tools, and how to turn your own API into something the agent can call. ::: ::: card Find the bad answers first [Testing and publishing](/build/testing) — what to test, the two failure modes worth hunting, and the readiness checks. ::: ::: card Put it in front of customers [Deployments](/build/deployments) — the lifecycle, what activation does, and why editing a live deployment clones it. ::: ::: ---------------------------------------------------------------------------- ## Knowledge bases URL: https://docs.amomic.in/build/knowledge ---------------------------------------------------------------------------- A knowledge base is a container of sources. You point it at your website, your documents, your pasted text, or your live systems; the platform reads all of it, splits it into passages, and indexes each passage by meaning. When a customer asks something, the closest passages are found and put in front of the model before it answers. That is the whole mechanism, and understanding it is what separates an agent that answers correctly from one that confidently invents a refund window. ```mock {"component": "RetrievalDiagram", "props": {"caption": "A question becomes a meaning-based search over your passages. The closest ones are put in front of the model, which answers from them."}} ``` ## Indexed by meaning, not by keyword This is the part that surprises people, so it comes first. Your content is not stored as searchable text. Each passage is converted into a numeric representation of what it *means*, and a customer's question is converted the same way. Retrieval finds the passages whose meaning sits closest to the question. The practical consequences: - **Wording does not have to match.** A page that says "orders are dispatched within 48 hours of confirmation" answers "when will you ship my stuff?" without sharing a single significant word. - **Adding more content can make answers worse.** Every passage competes. A knowledge base holding your help centre answers well; the same knowledge base with your entire marketing site added answers worse, because press releases and careers pages now sit between the question and the passage that holds the answer. - **Structure matters more than volume.** A passage that reads as a complete thought retrieves well. A passage that is half of a table, or a heading with no body under it, does not. - **There is no keyword fallback.** If a passage does not exist, no amount of exact phrasing will conjure it. "It should have found that" almost always means "that page was never crawled" or "that fact is inside an image". ## The five source types There are exactly five. Choose by asking what kind of thing you are teaching the agent. | Type | Console tab | Use it for | Refreshable | |---|---|---|---| | `website` | **Website** | Anything already published on your site — help centre, FAQ, policies, product pages. | Yes | | `file` | **Files** | Documents that exist as documents — a price list, a policy PDF, an onboarding deck. | No | | `text` | **Text** | Facts that live nowhere else. The three-line exception nobody wrote down. | No | | `endpoint` | **API** | Data from your own systems, either ingested on a schedule or looked up live mid-answer. | Yes, when ingested | | `mcp` | **MCP** | An MCP server whose tools become abilities the agent can call. | No | ```mock {"component": "KnowledgeScreen", "props": {"caption": "A knowledge base with its sources, their status, and which agents are connected to it."}} ``` ### `website` Paste one URL. The crawler starts there and reads the same site. **What it does:** - Honours `robots.txt`, then looks for a sitemap, then falls back to following links within the site. - Stays on the same site — the exact host or a subdomain of it, ignoring `www.`. It will not wander onto a different domain. - Renders JavaScript, so single-page applications and content that loads after the initial HTML are read correctly. - Skips cart, checkout, and admin paths, and skips binary assets. - Drops query strings when deciding whether it has already seen a page, so `?utm_source=` variants do not produce duplicates. - Reads up to about sixty pages per source. **What it does not do:** there is no crawl-configuration screen. You do not set a page limit, a crawl depth, include or exclude patterns, or a JavaScript toggle — those are not exposed anywhere in the product. The only input is the starting URL. That constraint is exactly why the starting URL matters so much. See [Scope the crawl narrowly](#scope-the-crawl-narrowly) below. **When a crawl fails**, the source carries the reason in plain words rather than a code: | What you see | What it means | |---|---| | `That address can't be reached from the crawler.` | The URL does not resolve, or the site is not reachable from the public internet. Staging behind a VPN will fail here. | | `Couldn't read any pages from that website.` | The crawler reached the host but got nothing usable — often a hard block on automated traffic. | | `We reached the site but couldn't read any text on it.` | The pages rendered but contain no readable text. Usually a site built entirely from images or an unusual embed. Paste the content as `text` or upload the documents instead. | ### `file` Upload documents directly. Accepted: `pdf`, `docx`, `doc`, `xlsx`, `xls`, `csv`, `txt`, `md`, `html`, `htm`, plus the image formats `png`, `jpg`, `jpeg`, `webp`, `gif`. **Maximum 15 MB per file.** Images are read visually and converted into text, which means a screenshot of a price table can become usable knowledge. It also means a 40-page PDF of scanned pages will work but produce messier passages than the same content as real text. If you have the source document, upload that rather than a scan of it. Files are never re-read on a schedule. When the document changes, upload the new one and delete the old — otherwise both versions are in the index and the agent will sometimes retrieve last year's prices. ### `text` A title and a body, typed or pasted. Underrated. Use it for the things that are true but unpublished: the actual escalation path, the exception you make for enterprise customers, the fact that the number on the contact page is no longer monitored. These are usually the highest-value passages in a knowledge base because they exist nowhere the crawler can reach. Keep each text source to one topic with a descriptive title. `Shipping policy` retrieves better than `Notes`. ### `endpoint` Your own HTTP API, in one of two modes: - **Ingest the response** — fetched now and on a schedule, converted to text, and indexed as passages. This is knowledge: a product catalogue, a branch list, a rate card. - **Call it live** — never ingested. Registered as a function the agent calls mid-answer for data that changes by the minute: stock, availability, a specific order's status. A live endpoint is where a knowledge base stops being knowledge and starts being a tool. It also carries a capability — `read` for a lookup, `action` for something that changes state in your system. [Tools and actions](/build/tools) covers both modes, the capability rules, and the safety model in full. ::: note There is no "test connection" button Neither the API tab nor the MCP tab has a verify or test control. You add the source and watch its status: it moves to `Connected` when it worked, or shows `Failed` with the reason. Use the [retrieval tester](#the-retrieval-tester) and the agent's test tab to confirm behaviour. ::: ### `mcp` Point at an MCP server URL and the tools it lists become abilities your agent can use. Because MCP does not tell us which of a server's tools change data and which only read, you classify the **whole server** once — look-up or action — and every tool on it inherits that classification. You can optionally restrict which tool names are allowed; leaving that empty allows everything the server lists. Again, see [Tools and actions](/build/tools) — an MCP server is a tool surface, not a body of knowledge. ## What happens to your content Every ingested source goes through the same pipeline. ::: steps ::: step Read The crawler fetches pages, the file reader extracts text from documents and images, the endpoint fetcher converts a response into readable lines. ::: ::: step Split into passages The text is cut into passages of roughly 1,600 characters, split on paragraph and line boundaries rather than mid-sentence. Each passage carries the last 200 characters of the one before it, so a fact that straddles a boundary is not lost. Fragments under 80 characters are discarded. A single source produces at most 1,500 passages. ::: ::: step Index by meaning Each passage is indexed together with its title, so a passage from a page called *Returns and refunds* is easier to find for a refund question than the same words on an untitled page. ::: ::: step Serve The passages become searchable for every agent the knowledge base is attached to. Re-syncing is last-good-serving: the old passages keep answering until the new ones are fully indexed, so a re-crawl never leaves an agent briefly knowing nothing. ::: ::: When a customer asks a question, one nearest-neighbour search runs **per attached knowledge base** and the results are merged, closest first. Roughly the six best passages reach the model. Passages that are not close enough to the question are dropped rather than padded out — which is why a `grounded` agent says it does not know instead of answering from something vaguely related. On voice calls there is one extra step: the knowledge base is also compressed into a compact fact sheet that the agent holds in mind for the whole call, so ordinary questions are answered without a lookup round-trip. Anything the fact sheet does not cover triggers a real search through the `search_knowledge` tool. This is why voice feels faster than the same question on chat, and why a very large knowledge base still answers common questions instantly. ## Source status Each source shows where it is in that pipeline. | Underlying status | What you see in the console | Meaning | |---|---|---| | `pending` | `Syncing…` | Queued, not started. | | `crawling` | `Crawling {done}/{found}` | Website only. Live progress — pages read out of pages discovered. | | `processing` | `Syncing…` | Text extracted; passages being indexed. MCP tool discovery also happens here. | | `ready` | `Synced ✓` or `Connected` | Serving. `Synced ✓` for ingested sources with a passage count; `Connected` for live endpoints and MCP servers, which have no passages by design. | | `error` | `Failed` | Something went wrong. The row shows the reason inline. | A source that shows `Connected` with zero passages is working correctly — a live tool has nothing to ingest. ## Refresh scheduling Ingested sources can re-read themselves on a schedule. The **Keep it fresh** control offers exactly four options: | Console label | Value | Interval | |---|---|---| | Only when I re-sync | `manual` | Never automatic. The default. | | Every day | `daily` | 24 hours | | Every week | `weekly` | 7 days | | Every month | `monthly` | 30 days | ::: warning There is no hourly option The shortest automatic interval is `daily`. If your content changes more often than that, a scheduled re-crawl is the wrong tool — use a **live** `endpoint` source, which fetches at answer time and is always current. ::: **Only two source types can be scheduled:** `website`, and `endpoint` in ingest mode. Text, files, and MCP servers are always `manual` — there is nothing to re-read for the first two, and a live tool is current by definition. A few behaviours worth knowing: - The next-refresh clock is pushed forward *before* a refresh starts, so a source that fails does not hot-loop. It retries at its next scheduled interval. - A source already syncing is skipped by the scheduler, and a manual re-sync on it is refused with `This source is already syncing.` - After a successful refresh, every agent attached to that knowledge base picks up the new content — including a rebuilt voice fact sheet. You do not republish the agent for a content refresh. You can always force a refresh: hover a source row and choose **Re-sync**. Admin or owner only. ## Attaching knowledge to an agent **An agent can only answer from knowledge you have explicitly attached to it.** Creating a knowledge base does not make any agent smarter. There is no default, no workspace-wide pool, no implicit access. Attach from either end: - From the agent's **Knowledge** tab — choose a knowledge base and **Attach**. - From the knowledge base's **Connected agents** card — **Connect**. Each attachment row shows the knowledge base's serving state as a raw value: | State | Meaning | |---|---| | `empty` | Attached, but it has no content yet. An agent whose knowledge requirement is `required` will not pass readiness. | | `ready` | Serving content. | | `degraded` | Serving, but something is not fully healthy. | | `unavailable` | Not currently serving. | The same knowledge base can teach several agents at once, which is the normal shape — one *Company policies* knowledge base attached to your support agent, your receptionist, and your order desk. **Bindings are versioned with the agent.** Attaching or detaching changes the draft; the change reaches customers when you publish a new version. A content *refresh* is different — a validated refresh advances the knowledge base's active revision without changing the binding, so new content flows to live agents without a republish. Detaching is a soft archive, not a deletion. The knowledge base and its content are untouched; the agent stops seeing it on the next published version. ::: danger Deleting is immediate and global Deleting a knowledge base stops its passages serving **every** connected agent at once, including live ones, without a republish. The console tells you how many passages will stop serving. Deleting a single source removes its passages from every agent's memory the same way. There is no undo. ::: ## Credentials for authenticated sources Endpoint and MCP sources can carry up to ten HTTP headers — an API key, a bearer token, a signed cookie. - Values go **straight into encrypted secret storage** at the moment you add the source. They are never written into the knowledge base record, never returned by any read, and never shown again in the console. - The console keeps only the header *names*, plus a `Key secured` indicator on the row. - To rotate a credential, re-enter it on the source. The pointer does not change, so nothing else needs updating. - Auth is header-based only. There is no OAuth flow and no client-certificate option. - If a credential goes missing, the source reports `This source's credentials are missing; re-enter them on the source.` rather than failing silently mid-conversation. The platform also refuses to store credential-shaped fields anywhere they do not belong — a deployment or channel configuration containing a key called `token`, `password`, `api_key` or similar is rejected outright. Secrets have exactly one place to live. ## Getting good answers Adding sources takes minutes. Getting a knowledge base that answers your customers' actual questions takes about an hour of deliberate work. This is that hour. ### Scope the crawl narrowly Since there is no crawl-configuration UI, the starting URL is your only control — and it is a strong one, because the crawler stays on the same site and reads a bounded number of pages. - Point it at `https://yourcompany.com/help`, not `https://yourcompany.com`. - If your help centre lives on its own subdomain, that is even better. - If your policies, your blog, and your careers page all live under one root, add *separate sources* for the specific sections rather than one source for the root. Two reasons. First, retrieval quality: fewer irrelevant passages means the right one wins more often. Second, the page budget: sixty pages spent on your help centre is a complete help centre, while sixty pages spent from your homepage is your navigation, your blog index, three press releases, and possibly no policy page at all. ::: tip Check what actually got read After the crawl finishes, look at the passage count on the source row. A twelve-page help centre that produced eight passages did not work — go and look at those pages in a browser with JavaScript disabled, and you will usually find the content arrives from somewhere the crawler could not follow. ::: ### Prefer structured pages Content that retrieves well shares a shape: a clear heading, a self-contained answer beneath it, in prose. Content that retrieves badly: - **Answers buried in tables.** A shipping matrix with regions across the top and weights down the side is perfectly readable to a human and nearly meaningless once flattened into a line of text. Rewrite the ten most-asked rows as sentences in a `text` source: "Delivery to Mumbai for orders under 5 kg takes 2 to 3 working days." - **Answers inside images.** A price card exported as a PNG, an infographic of the returns process, a screenshot of the timings board. Images uploaded as files are read visually and will work; the same image embedded in a crawled web page will not be read. This is the single most common cause of "it should have known that". - **Answers in PDFs behind a login.** The crawler is an anonymous visitor. Upload the file instead. - **Accordion and tab content that loads on click.** JavaScript is rendered, but content that requires a click to fetch may not be there when the page is read. Check with the retrieval tester. - **One enormous page.** A 20,000-word policy document becomes many passages that all look alike. Split it, or add a short `text` source with the ten facts customers actually ask about. ### Say the exceptions out loud Your published content describes the policy. Your team knows the exceptions. Customers ask about the exceptions. Write a `text` source for each one. "Refunds are 14 days, but for orders shipped to the North-East the clock starts on delivery, not dispatch." Nothing on your website says that, so nothing in your index does either, and the agent will confidently quote 14 days from dispatch. ### The retrieval tester Every knowledge base has a **Retrieval tester** — a search box that shows exactly which passages the agent would look up for a given question, without involving the model at all. This is the sharpest debugging tool in the product, because it separates two failures that look identical from the outside: - **The tester returns nothing relevant** — `Nothing relevant found — the agent would say it doesn't know.` The passage does not exist or is not close enough. This is a *content* problem. Add or rewrite a source. - **The tester returns the right passage but the agent still answered badly** — retrieval worked and the model did not use it. This is a *persona* problem. Look at the purpose, the instructions, and the grounding policy in [Agents and versions](/build/agents). Every member of the workspace can use the retrieval tester, including those who cannot edit knowledge. ### Then test as a customer The retrieval tester tells you what was found. The agent's **Test** tab tells you what the customer hears. Use both, in that order, and use the questions your team gets wrong rather than the ones you know are covered. [Testing and publishing](/build/testing) covers what to look for. ## Limits | Limit | Value | |---|---| | Knowledge bases per workspace (hard backstop) | 50 | | File upload size | 15 MB | | Passages per source | 1,500 | | Endpoint or MCP response size | 2 MB | | Tools listed per MCP server | 20 | | Headers per source | 10 | Plan tiers cap how many knowledge bases you can create below that backstop — see [Limits and plans](/operate/limits). Sources, passages, and crawl pages are not capped by plan. ## Who can change knowledge Creating a knowledge base, adding or deleting sources, renaming, re-syncing, and connecting or disconnecting agents are all **admin or owner** actions. Members see the knowledge bases, their sources, and the retrieval tester, but the write controls are not shown to them at all. See [Workspaces and roles](/concepts/workspace). ## Where to go next ::: cards ::: card Turn a source into an ability [Tools and actions](/build/tools) — live endpoints, MCP servers, and the difference between reading and doing. ::: ::: card Find the gaps before customers do [Testing and publishing](/build/testing) — the two failure modes worth hunting, and readiness. ::: ::: card Tune the agent that reads it [Agents and versions](/build/agents) — purpose, instructions, and the grounding policy. ::: ::: ---------------------------------------------------------------------------- ## Tools & actions URL: https://docs.amomic.in/build/tools ---------------------------------------------------------------------------- Knowledge lets an agent answer. Tools let it *act*. A tool is a named capability the agent can decide to invoke mid-conversation — opening a ticket, sending a message, looking up a live order, cancelling a booking — and the difference between a helpful agent and a merely articulate one is usually a tool. This page covers the four tools that ship built in, how to build your own on top of a knowledge base, and the policy layer that decides what any of them may do. ## What a tool is To the model, a tool is a name, a description of when to use it, and a set of typed parameters. On each turn it may either answer or call a tool; if it calls one, the result comes back and it continues from there. The loop runs at most three rounds before the agent is forced to answer with what it has, so a tool that misbehaves cannot trap a customer in silence. Three properties are worth internalising before you build anything: - **The description is the control surface.** The agent decides whether to call a tool almost entirely from its description. "Check stock" invites speculative calls; "Use when the customer asks whether a specific product is currently available, after they have named the product" does not. - **A tool the agent cannot see cannot be called.** Disabling a tool does not add a rule telling the model to avoid it — the tool is never declared, so it does not exist as far as the model is concerned. That is a much stronger guarantee than an instruction. - **Tool failures never surface as errors.** If a tool throws or times out, the failure is handed back to the model as text and it recovers in conversation. A customer sees "I could not reach our order system right now" rather than a stack trace or an abrupt silence. ## The built-in tools Four tools ship with the platform. These are their exact registered names — what the model sees, and what you will see in a transcript. | Tool | Available on | Enabled by default | |---|---|---| | `create_support_ticket` | Every channel | Yes | | `send_whatsapp_message` | Every channel | No | | `search_knowledge` | Voice calls only | Yes, when the agent has knowledge | | `end_call` | Voice calls only | Yes | ### `create_support_ticket` Creates a support ticket and resolves or creates the customer's contact record in one guarded action. It returns a status and the ticket's public reference — `AM-4F91C2E0` — which the agent can read back to the customer. **When the agent reaches for it:** when the customer asks for a human, asks for follow-up, asks to escalate, or has an unresolved problem that needs staff action. It is the mechanism behind every "let me get someone to look at this". **How identity is resolved**, per channel — the agent never asks for information the channel already knows: | Channel | What it uses | |---|---| | Web chat | Asks first name, then email; asks for a phone number only when creating a new contact | | WhatsApp | The chat's own number. Asks for an email only if the contact record has none | | Voice | The caller's number, automatically. The email and phone parameters are **structurally removed** from the tool on calls | | Email | The sender's address, automatically | That voice behaviour is deliberate and worth understanding. Speech recognition is not reliable enough to transcribe an email address or a phone number correctly, so rather than accepting a plausible-looking wrong address, the fields are removed from the tool entirely. The caller ID is the identity. **Ticketing is idempotent within a conversation.** A retry — the customer asking again, the model calling twice — returns the same ticket rather than opening a duplicate. The agent is also instructed never to claim a ticket exists until the tool has actually returned success. If ticket creation fails, the customer is told it failed. See [Tickets](/api/tickets) for what happens next, and [Escalate to a ticket](/recipes/support-escalation) for the end-to-end shape. ### `send_whatsapp_message` Puts a confirmation, a link, or a reference on the customer's phone — **from any channel**. A caller on the phone can be sent the booking reference by WhatsApp before they hang up. A web visitor can be sent the tracking link. It is **off by default**, including for agents created before it existed. Turn it on from the agent's **Tools** tab. **Who it can message.** By default, only the person the agent is already talking to. On WhatsApp itself, the customer's own WhatsApp ID is authoritative — if the model produces a different recipient, that recipient is ignored outright. There is one setting, **Allow messaging a different number**, that permits a model-chosen destination; it exists for cases like an order desk notifying a third party, and it is the one control that lets a customer's wording decide where a message goes. Leave it off unless you specifically need it. **Which templates it may send.** You choose an explicit allowlist, up to 20 templates. That list becomes the set of values the `template_name` parameter will accept — the model literally cannot name a template you did not approve. If no template is approved yet, the agent can only reply inside an open WhatsApp conversation. #### The closed-window result WhatsApp permits free-form messages only within 24 hours of the customer's last inbound message. Outside that window, only pre-approved templates go out. See [The 24-hour window](/channels/whatsapp-window) for the rule itself. What matters here is how the tool behaves when it hits that wall. It does not fail silently, and it does not quietly substitute different words. It returns a structured status the agent can reason about: | Status | What happened | |---|---| | `sent` | The message went out. | | `template_required` | The window is closed. Free text is not permitted; a template is needed. | | `template_not_allowed` | A template was named, but it is not on this agent's allowlist. | | `template_not_found` | The named template does not exist or is not approved. | | `needs_information` | A required value is missing — a template variable, a recipient. | | `opted_out` | The recipient has opted out of messages from you. | | `needs_setup` | WhatsApp sending is enabled but no sender number is usable. | | `not_configured` | WhatsApp is not connected for this workspace. | | `failed` | The send was attempted and did not succeed. | **The practical consequence:** on `template_required`, the agent knows *why* it could not send and can do something sensible — send an approved template instead, or tell the customer plainly that it cannot message them right now and offer another route. It does not tell the customer the message went out when nothing was sent. If you have approved no templates, this is the moment your agent starts apologising to customers at 2 a.m.; approving one utility template is usually the whole fix. ::: warning No template is sent automatically when a window closes There is no hold-and-release queue and no automatic re-engagement. If the window closes with a message pending, the message does not go out later. Either the agent sends an approved template in the moment, or nothing is sent. ::: ### `search_knowledge` **Voice calls only**, and only when the agent has knowledge attached. On a call, the agent already holds a compressed fact sheet built from its knowledge base, so ordinary questions are answered without any lookup — which is what keeps a call feeling like a conversation rather than a query. `search_knowledge` is the long tail: when the caller asks for a specific detail the fact sheet does not carry, the agent searches the full knowledge base for it. You do not configure this tool. It appears when the agent has content and disappears when it does not. On chat channels there is no equivalent tool because retrieval is not a decision there — the relevant passages are put in front of the model before it answers. See [Knowledge bases](/build/knowledge). ### `end_call` **Voice calls only.** Hangs up, after the agent's goodbye has finished playing. This exists because a phone call needs an ending. Without it, an agent that has said goodbye sits on an open line waiting for a caller who has stopped talking, and the call ends on an idle timeout instead of a clean farewell. The tool waits for the spoken goodbye to complete before disconnecting, so the caller never hears themselves cut off mid-word. ## Building your own tools Your own tools are **knowledge sources**. There is no separate integrations screen, no plugin directory, and no third-party connector marketplace — a tool is an `endpoint` or `mcp` source on a knowledge base, and it reaches an agent through the same attachment that gives it knowledge. That is a deliberate design: the thing that decides what an agent knows also decides what it can do, so there is exactly one place to audit. ### Read tools versus action tools Every custom tool declares a capability. | Capability | Meaning | Example | |---|---|---| | `read` | Looks something up. Nothing in your system changes. | Order status, stock level, appointment availability, invoice balance | | `action` | Changes something in your system. | Accept a return, cancel a booking, reschedule a delivery, apply a credit | In the console these read as **Look up data** and **Take an action**, and a source configured as an action carries an `Action` badge wherever it appears. The distinction is enforced, not advisory: - A `read` tool may only use `GET` or `POST`. Choose `PUT`, `PATCH`, or `DELETE` and you are told `{method} changes data — set capability to 'action' to use it.` - An `action` must be a live tool. It cannot be a scheduled ingest, because ingesting a `DELETE` on a timer is not a thing anyone wants. - A live tool must have a name the agent can call. ### A live read tool Mode **Call it live**, capability **Look up data**. It is never ingested into the knowledge base; it is registered as a function the agent invokes mid-answer. You provide: - **The URL**, optionally with `{braces}` for values the agent fills in — `https://api.yourcompany.com/orders/{order_id}`. Each substituted value fills exactly one path segment; it cannot escape into the rest of the path. - **A tool name** — a short verb, like `check_stock`. This is what appears in transcripts. - **When should the agent use it?** — plain words. This is the field that stops the agent calling too eagerly, and it deserves more thought than the URL. - **Headers**, if the endpoint needs auth. Values go into encrypted storage immediately and are never shown again. The response is capped at 2 MB, and what reaches the model is truncated at 24,000 characters. Return the fields the agent needs, not your full object graph — a lean response is both faster and easier for the agent to use correctly. ::: tip Write the description as a rule, not a label `check_stock` with the description "Checks stock" will be called for "do you have anything nice?". The same tool with "Use only after the customer has named a specific product, to report whether that product is currently in stock" will not. Every eager tool call in production traces back to a description that described the endpoint instead of the situation. ::: ### An action tool Mode **Call it live**, capability **Take an action**. Same fields, plus a method that may be `POST`, `PUT`, `PATCH`, `DELETE`, or `GET`. When any action tool is bound to an agent, extra rules are injected into its behaviour automatically: - Confirm the customer actually asked for this before doing it. - Gather missing details one at a time, not as a form. - Never claim it is done before the tool returns success. - Never call speculatively. **Every action call is audited** — success or failure — with the workspace, agent, conversation, channel, deployment, source, tool name, capability, outcome, and latency. **Arguments and response bodies are deliberately not recorded**, because they carry customer data and, occasionally, credentials. You get a complete record of what happened without a second copy of the payload. Read-capability calls are not audited; nothing changed. ### MCP servers An MCP server is added once and its tools become available to any agent the knowledge base is attached to. Up to 20 tools per server, and you can restrict which of them are allowed by name. Because MCP does not declare which of a server's tools mutate state, **you classify the server, not the tool** — look-up or action — and every tool on it inherits that classification. If any tool on the server changes data, the whole server is an action server. That is a conservative default and it is the correct one. Errors are reported in plain words: `The MCP server rejected our credentials (401).`, `The MCP server answered with HTTP {code}.`, `Couldn't reach the MCP server. Check the URL and try again.`, and `The MCP server lists no tools.` ### Safety at call time Every custom tool call, read or action, runs through the same guards: - The destination must resolve to a public address. Internal addresses, loopback, and cloud metadata endpoints are refused. - Redirects are not followed, and the connection is pinned to the address that was checked — so a host cannot pass a check and then point somewhere else. - Responses are capped at 2 MB, and what reaches the model at 24,000 characters. - Credentials are read from encrypted storage at call time and are never present in any record you can read. ## The tool policy The agent's **Tools** tab holds a small policy that is **frozen into every published version**. That is the point: what an agent was allowed to do on a given day is recoverable, not inferred. | Setting | Default | Effect | |---|---|---| | Create support tickets | **On** | Enables `create_support_ticket`. | | Send WhatsApp messages | **Off** | Enables `send_whatsapp_message`, plus its sender, template allowlist, and recipient rule. | | Let this agent take actions | **On** | Master switch for action-capability tools on attached knowledge bases. | | Write actions | `Approved actions only` | Not configurable. The model cannot choose the workspace, the channel identity, or the contact id. | **The actions master switch is absolute.** Turned off, action tools on an attached knowledge base are not declared to the model at all — it cannot try and be refused, because it never learns they exist. Read tools continue to work normally, so an agent with actions blocked can still look up an order but cannot cancel one. Turn it off when you want an agent that answers questions about a system it must not touch. Tools are also only offered when their source is actually serving. A source that is still syncing or has failed contributes nothing, so a broken integration degrades the agent rather than breaking the conversation. ::: note What is deliberately not here There is no per-deployment tool override — a version's tool policy applies to every channel it runs on. There is no approval workflow for individual tool calls, and there is no per-agent-version allowlist beyond the WhatsApp template list. If two channels need different powers, build two agents. ::: ## What happens automatically Three behaviours are not settings and cannot be turned off, because turning them off would only produce bad data: - **Contact resolution.** Before creating anything, the agent resolves the customer against your directory by email, phone, and WhatsApp ID. A returning customer enriches the record that already exists. See [Conversations and contacts](/concepts/conversations). - **Idempotent ticketing.** Retries within the same conversation return the same ticket rather than duplicates. - **Hand-off context.** When a ticket is opened, the issue summary, the channel, the requester, and the conversation reference go with it, so the person who picks it up is not starting from nothing. ## Reading what a tool did Every conversation in the [inbox](/concepts/conversations) shows an operational trail alongside the messages, and tool use appears there by kind: `Knowledge base`, `Live API`, `MCP tool`, and `Action`. That trail is staff-facing only — it is not part of the transcript a customer sees, and it is not returned by [`GET /conversations/{id}/messages`](/api/conversations). When an agent gives a wrong answer, that trail is the first thing to read. It tells you immediately whether the agent looked anything up, called your API, or answered from memory — three very different bugs with three very different fixes. ## Where to go next ::: cards ::: card Add the sources tools live on [Knowledge bases](/build/knowledge) — endpoint and MCP sources, credentials, and refresh. ::: ::: card Get the WhatsApp side right [The 24-hour window](/channels/whatsapp-window) and [Templates](/channels/whatsapp-templates) — what the send tool is allowed to do and when. ::: ::: card Watch the tools fire [Testing and publishing](/build/testing) — the test tab shows what the agent looked up on every turn. ::: ::: card Handle the escalation end [Escalate to a ticket](/recipes/support-escalation) — from the agent's tool call to your team's queue. ::: ::: ---------------------------------------------------------------------------- ## Deployments URL: https://docs.amomic.in/build/deployments ---------------------------------------------------------------------------- Publishing freezes an agent into a version. Deploying puts that version somewhere a customer can reach it. A **deployment** is the join between the two — this agent version, on that channel connection, with this channel-specific configuration — and it is the only object in the platform that decides what a real phone number, WhatsApp sender, or website widget actually does. ```mock {"component": "DeploymentDiagram", "props": {"caption": "An agent version plus a channel connection plus channel configuration equals a deployment. Activating one supersedes whatever was serving that connection."}} ``` ## The three ingredients ```text agent version + channel connection + channel config = deployment (what it says) (where customers (greeting, call reach you) language, domains) ``` **The agent version** is immutable. A deployment names one specific version, and that version cannot change underneath it. See [Agents and versions](/build/agents). **The channel connection** is a specific touchpoint you own and have verified: a website, a WhatsApp Business number, a phone number. Connections are created and verified in the console, not through the API. **The channel config** is the part that genuinely differs per channel and has nowhere else to live: | Channel | Configuration that lives on the deployment | |---|---| | Web | The domains the widget may load on, the widget's appearance and greeting, lead capture | | WhatsApp | Which sender number this agent answers from | | Voice | The phone number, the call language, the language the call opens in, the opening line, whether calls are recorded | One agent can have many deployments — a widget on your main site, a second on your help centre, a WhatsApp number, a phone number — all serving the same published version. Each is independent. Pausing the phone deployment does not touch the widget. ## The lifecycle ```text draft → ready → active → superseded └→ archived ``` | State | What it means | |---|---| | `draft` | Created and editable. Nothing is serving. This is the only state in which a deployment can be changed. | | `ready` | It has passed validation and may be activated. **Already frozen** — see [the clone rule](#editing-a-live-deployment-clones-it). | | `active` | It is the deployment its connection points at. Customers reach it. | | `superseded` | A newer deployment took over the same connection. It is history, and it is the thing rollback restores. | | `archived` | Removed from the working set. The connection and past conversations are untouched. | Two further dimensions sit alongside the lifecycle. **Desired state** is `active` or `paused` — this is what pausing changes, and it moves independently of the lifecycle. **Health** is `unknown`, `healthy`, `degraded`, or `error`, reported by the channel rather than set by you. A freshly activated deployment starts at `unknown` and stays there until the channel says otherwise; that is not a problem. ## Validation, and what blocks activation A deployment is validated before it can be activated, and again at the moment of activation. If anything is missing you get a specific list rather than a generic failure. The checks are: - **The agent version exists.** You cannot deploy an agent that has never been published. - **The connection is enabled** and **verified**. A number that has not completed verification cannot serve customers. - **The provider authorisation is healthy** — the underlying account connection has not expired or been revoked. - **The channel configuration is complete** for that channel: - **Web** — allowed domains are set, and the widget install is verified. - **WhatsApp** — a sender number is chosen, and its webhook is verified. - **Voice** — a phone number is chosen, and an opening line is written. In the deployment dialogs the same conditions appear as plain-language reasons on each option: `Connection disabled`, `Verification required`, `Connection has an error`, `Connection is degraded`. If the agent itself is not published yet, the dialog says so directly — `Publish the agent before deploying it to a channel.` ## What activation does Activation is one transaction, and everything in it either happens together or not at all. ::: steps ::: step Re-validate The checks above run again. Anything unresolved and activation is refused with the list of blockers. Nothing has moved. ::: ::: step Claim the connection The connection is claimed with a compare-and-swap on its own version. If someone else changed that connection between the moment you loaded the page and the moment you pressed the button, activation fails with `Connection changed since it was loaded.` rather than overwriting their change. This is what makes two people activating different agents on one phone number at the same time impossible. One wins; the other is told why. ::: ::: step Swap and supersede The new deployment becomes `active`. The connection is repointed to it. **The deployment that was serving that connection becomes `superseded` and `paused`** in the same transaction. Nothing on any other connection moves. Activating a WhatsApp deployment does not touch your phone number. ::: ::: step Project onto the channel The live configuration is pushed to the channel runtime: the widget starts serving the new agent on the allowed domains, the phone number starts routing calls to it with its voice settings, the WhatsApp sender starts answering from it. ::: ::: There is no partial state. A customer either reaches the old deployment or the new one, never a half-configured hybrid. ::: note Activation is idempotent Activating a deployment that is already active on that connection returns success and changes nothing. A double-click, or two operators pressing the button at the same time, is safe. ::: ## Editing a live deployment clones it This is the rule worth learning before anything else. **An active deployment is immutable.** So is a `ready` one — validation freezes it, not activation. Attempting to edit either is refused: `Active or validated deployments are immutable; clone to make changes.` The supported path is: ```text clone → validate → activate ``` The clone copies the channel, the connection, the agent, and the agent version, takes your replacement configuration, and lands as a fresh `draft`. It must pass validation before it can be activated, and activating it transactionally supersedes the original. The console does this for you. When you change a voice deployment's settings, it tells you plainly: *Saving rolls a new deployment onto this number so the change is auditable. Calls already in progress finish on the old settings.* You are not editing the running configuration; you are building its replacement and swapping it in. ### Why this matters **A bad edit can never take down a working number.** Consider the alternative. If deployments were mutable, saving a voice deployment with an empty opening line would break the number immediately, and every caller for the next ten minutes would hear silence. With cloning, the worst case is a clone that refuses to validate. The clone sits in `draft` telling you what is wrong. The original deployment carries on answering calls exactly as it did before, because nothing touched it. You fix the clone, or you abandon it. **The second reason is auditability.** Every configuration a number has ever served is a real object with a timestamp, not a diff you have to reconstruct. "What was this number doing on 14 June?" has an answer you can open. **The third is rollback.** Because the previous deployment still exists as `superseded` — complete with its configuration and its agent version — going back is re-activating a thing that demonstrably worked, not rebuilding it from memory. ::: warning A live agent update is two steps, not one Editing the agent and publishing a new version does **not** change what any deployment serves. A deployment names one specific version. After publishing, you must deploy the new version to each connection you want it on. Until you do, the agents list will show `Changes to publish` cleared and your customers still hearing the old behaviour — which is exactly the confusion this design trades for safety. ::: ## Pausing Pausing is the fast, reversible way to take an agent off a channel. **What it does:** sets the deployment's desired state to `paused` and **un-points the channel from it**. The phone number stops routing calls to the agent, the WhatsApp sender stops answering, the widget stops serving. New traffic is not handled. **What it does not do:** - It does not change the lifecycle state. The deployment is still `active`; it is not desired to be serving. That is why resuming is instant and requires no re-validation. - It does not delete or alter the configuration. - It does not touch conversation history, transcripts, tickets, or contacts. - It does not affect any other deployment, including other deployments of the same agent. Resume puts it straight back. Pause is the right tool for "stop it now, decide later" — an agent giving bad answers at 11 p.m. should be paused, not edited. You must pause before archiving. Archiving a serving deployment is refused with `Pause this deployment before removing it.` — the platform will not let a tidying action drop a customer's call. ## Rolling back The previous deployment on a connection is still there, marked `Replaced`. Rolling back to it clones it, validates, and activates in one step, superseding the deployment that is currently live and restoring both its configuration *and* its agent version. Rollback is a mitigation, not a fix. Your draft still contains whatever caused the problem. Correct it, test it, publish a new version, and deploy forward. ## Run-state badges The **Deployments** tab shows one of five strings per row. These are the exact strings. | Badge | Meaning | What to do | |---|---|---| | `Live` | Active and serving. Customers reach it. | Nothing. | | `Disabled` | Paused. Configuration intact, not serving. | Resume it when you are ready. | | `Error` | The channel reports it as unhealthy. | Check the connection — verification, provider authorisation, credentials. | | `Replaced` | Superseded by a newer deployment on the same connection. | Nothing. It is history, and it is what rollback restores. | | `Not live` | Not currently serving — typically a draft, or one that never activated. | Validate and activate it, or delete it. | ::: tip `Replaced` on every row but one is correct A connection that has been reconfigured five times has five `Replaced` deployments and one `Live`. That is the audit trail working, not a leak. Superseded and archived deployments do not count toward your plan's deployment limit. ::: ## Removing a deployment Removing takes the deployment out of the working set. The console is explicit about what survives: *This removes the {channel} deployment. The channel connection itself stays and past conversation history is kept — you can deploy an agent here again later.* So removing a WhatsApp deployment does not disconnect your WhatsApp number, and removing a voice deployment does not release your phone number. The connection is yours until you release it deliberately from the channel page. ## Deployment limits Your plan caps how many deployments can exist in the working set — `draft`, `ready`, and `active` all count. Superseded and archived history is free, so a well-used connection does not consume your allowance. When you hit the cap: `Your plan includes {limit} deployment(s). Upgrade to add more.` See [Limits and plans](/operate/limits). ## Where to go next ::: cards ::: card Publish something to deploy [Agents and versions](/build/agents) — what publishing freezes and why versions are immutable. ::: ::: card Check it before you activate [Testing and publishing](/build/testing) — readiness checks and the publish-then-deploy sequence. ::: ::: card Put it on a phone number [How voice works](/channels/voice) and [Numbers and KYC](/channels/phone-numbers) — the voice connection and its per-deployment settings. ::: ::: card Put it on WhatsApp [Set up WhatsApp](/channels/whatsapp) — connecting a number and verifying it before you deploy. ::: ::: ---------------------------------------------------------------------------- ## Testing & publishing URL: https://docs.amomic.in/build/testing ---------------------------------------------------------------------------- Every agent is good at the questions you thought of while building it. Testing is the half hour in which you find out what happens to the other ones — and it is the cheapest half hour in this entire product, because every failure you find here is a real customer conversation you do not have. ## The test tab Open the agent and choose **Test**. It is a real conversation with the current draft: the agent remembers the thread, so you can ask a question and then follow up on the answer, which is where most agents actually break. Three pieces of the interface do the work. **The source expander.** Every answer shows how long it took and either `{n} source(s)` — expandable to see exactly which passages it used — or the badge **`No knowledge used`**. That badge is the most informative thing on the screen. An answer full of specific facts about your business with `No knowledge used` next to it is an agent inventing your policy, and it will do the same thing to a customer tonight. **The verdicts.** Mark each answer `Pass` or `Needs work`. This is not bookkeeping — a passing run against the current configuration is what the readiness check looks for. Marking answers is how you tell the platform you actually looked. **New thread.** Because the agent remembers the conversation, a fresh question after five turns is not the same test as a fresh question at the start. Start a new thread when you change topic. Messages are capped at 1,000 characters, which is longer than any real customer message. ## What to actually test Do not test the questions you know are covered. You wrote the knowledge base; of course it answers those. Test the ones your own team gets wrong. ::: steps ::: step The five questions your support inbox is full of Open your inbox — the real one, with humans in it — and take the five most repeated questions from last month. Ask those, in the customer's own words, typos included. If the agent cannot handle your top five, nothing else matters. ::: ::: step The exceptions "What if I bought it on sale?" "What if I'm in Guwahati?" "What if the box was opened?" Published policies describe the rule; customers ask about the edges. An agent that answers the rule confidently when the customer is describing an exception is worse than one that says it does not know. ::: ::: step The follow-up Ask a question, then ask a second one that only makes sense given the first answer. "How long do refunds take?" then "And if I paid by UPI?" This is where thin knowledge shows: the first answer is fine, the second is invented. ::: ::: step The out-of-scope question Ask it something you sell nothing of. `Do you sell refrigerators?` A well-scoped agent declines cleanly. A vague one improvises. If it improvises, the fix is in the [purpose](/build/agents), not the knowledge base. ::: ::: step The hand-off Say `I want to talk to a human.` The agent should reach for a ticket, gather what it needs without interrogating you, and read back a reference. If it merely apologises, check that ticketing is enabled on the [Tools](/build/tools) tab. ::: ::: step The angry customer Type the message an actually annoyed person sends — short, capitalised, no punctuation. Tone handling is the difference between a complaint that resolves and a complaint that goes on social media. ::: ::: The console offers four starters if you want somewhere to begin: `How much does it cost?`, `Do you sell refrigerators?`, `I want to talk to a human.`, and `What are your support hours and how do I reach you?` They are a warm-up, not a test plan. ## The two failure modes Almost everything you find will be one of these two, and they need opposite fixes. Telling them apart is the whole skill. ### 1. It answers from nowhere The agent states a policy, a price, a timing, or a rule that is not in any of your sources. Usually fluent, usually plausible, occasionally exactly right by luck. **How to spot it:** the `No knowledge used` badge, or a source expander whose passages have nothing to do with the claim. **What it means:** your knowledge base is thin here and the model is filling the gap. **The fix is content.** Add the source. If the fact exists on your website but was not crawled, check whether that page is inside the section you pointed the crawler at. If it exists nowhere — the exception your team knows by heart — write a `text` source for it. See [Knowledge bases](/build/knowledge). Do not fix this by adding a rule to the instructions. "Never say the refund window is 30 days" patches one sentence and leaves the underlying gap intact. ### 2. It refuses something it should know The agent says it does not know, and you can see the answer on your own website. **How to spot it:** open the knowledge base's **Retrieval tester** and ask the same question. It shows exactly which passages would have been retrieved, with the model taken out of the picture. - **The tester finds nothing relevant** — the passage does not exist. Almost always one of: the answer is inside an image, the answer is inside a table that flattened into meaningless text, the page needed a click to load its content, or the page was outside the crawl. - **The tester finds the right passage but the agent still refused** — retrieval worked and the model did not use it. That is a persona problem: check the purpose for a boundary that excludes this topic, and consider whether `grounded` is too tight for the way that passage is written. Tables and images are the usual culprit, and worth stating plainly: a shipping matrix or a price card that a human reads at a glance frequently becomes unusable once flattened into text. Rewrite the ten rows customers actually ask about as sentences in a `text` source. It takes ten minutes and fixes more failures than anything else you can do. ## Readiness checks The agent's **Overview** tab shows a readiness verdict — `Ready to publish` or `Needs attention` — computed from four areas: | Check | What it looks at | |---|---| | **Agent** | The persona is complete enough to publish — it has a name and a purpose. | | **Knowledge** | If the knowledge requirement is `required`, at least one attached knowledge base is actually serving content. | | **Test** | A passing test run exists against the current configuration. | | **Tools** | The tool policy is coherent — for example, WhatsApp sending enabled with a usable sender. | Each issue labels itself `Required before publishing` or `Recommended`, with a link that takes you to the tab that fixes it. When everything passes you get one line: `Every current check passes.` ::: note The test check is fingerprinted to the configuration A passing run counts against the configuration it was run on. Change the persona, attach a knowledge base, or alter the tool policy and the test check goes stale — because a passing run on a different agent is not evidence about this one. Re-test after material changes. This is mildly annoying by design. ::: Recommended issues do not block publishing. They are there because someone will otherwise publish an agent with knowledge attached but no tools enabled and wonder why it never escalates. ## Publishing, then deploying These are two separate actions and the order matters. ::: steps ::: step Publish the version **Overview → Publish**, or the **Versions** tab. The current persona, knowledge bindings, and tool policy are frozen together into an immutable version. Write a change summary. `Added the 2026 returns policy and stopped it quoting delivery estimates` costs five seconds and is the only human-readable record of why this version exists. ::: ::: step Deploy it to each channel Publishing does not move anything. Until you deploy, the agent shows `Published, not deployed yet` and every live channel keeps serving the version it was already serving. From the agent's **Deployments** tab, deploy the new version to each connection you want it on. Each activation supersedes what was there in a single transaction. See [Deployments](/build/deployments). ::: ::: step Try it on the real channel The test tab tests the agent. It does not test the channel. Send yourself a WhatsApp message, load the page with the widget on it, or ring your own number. Channel-level problems are invisible in the studio: a domain missing from the widget's allowed list, a WhatsApp template that was never approved, an opening line that reads fine and sounds strange out loud. Five minutes on the real channel catches all three. ::: ::: ::: warning Roll out one channel at a time If you have four deployments, activate the new version on one, watch it for a few conversations, then do the rest. All four at once means a problem you did not catch in testing reaches every customer simultaneously — and while [rollback](/build/deployments) is fast, not needing it is faster. ::: ## When something is wrong in production In order, quickest first: 1. **Pause the deployment.** It stops serving immediately and nothing is lost. This is always the right first move at 2 a.m. 2. **Roll back.** The previous deployment is still there as `Replaced`, with its configuration and its agent version intact. Re-activating it restores exactly what worked. 3. **Then diagnose.** Read the conversation in the [inbox](/concepts/conversations) — the operational trail shows whether the agent looked anything up, called a tool, or answered from memory. Three different bugs, three different fixes. 4. **Fix the draft, test, publish, deploy.** Your draft still contains whatever caused it. Rollback bought time; it did not change anything you wrote. ## Where to go next ::: cards ::: card Fix a content problem [Knowledge bases](/build/knowledge) — the retrieval tester, source types, and getting good answers. ::: ::: card Fix a behaviour problem [Agents and versions](/build/agents) — writing a purpose, instructions, and the grounding policy. ::: ::: card Ship it safely [Deployments](/build/deployments) — activation, pausing, and rollback. ::: ::: card Check the whole picture [Going live](/operate/going-live) — everything worth confirming before real traffic arrives. ::: ::: ============================================================================ # SECTION: Channels ============================================================================ ---------------------------------------------------------------------------- ## Set up WhatsApp URL: https://docs.amomic.in/channels/whatsapp ---------------------------------------------------------------------------- Connecting WhatsApp puts one of your agents on a WhatsApp Business number you own. Customers message the number the way they message anyone else; the agent reads the message, answers from its knowledge base, and every exchange lands in your inbox alongside web and voice conversations. Nothing about the agent changes. The same published version, the same knowledge, the same tools — rendered in WhatsApp's own formatting instead of a web bubble. ## What you get once it is connected - **Inbound conversations answered automatically.** Every message to the connected number reaches the agent deployed to it. - **Outbound sends from your backend.** [`POST /messages`](/api/messages) sends free-form text or an approved template from any connected sender. - **The agent can start a WhatsApp conversation itself.** An agent talking to someone on your website can follow up on WhatsApp, if it has a phone number for them — see [Capture leads](/channels/web-chat-leads). - **Opt-out handling across the workspace.** A customer who sends `STOP` to one of your numbers is suppressed on all of them. ## Vocabulary: WABA, sender, agent Three things get confused constantly, and the difference matters when something breaks. | Term | What it is | |---|---| | **WhatsApp Business Account (WABA)** | Meta's container for your business messaging. It holds your message templates, your quality rating, and one or more phone numbers. You own it; Amomic-AI is authorized to act on it. | | **Sender** | One phone number inside that WABA, connected to your workspace. Each sender carries its own credential, its own assigned agent, and its own reply settings. | | **Agent** | The published [agent version](/build/agents) that answers on that sender. Bound by activating a [deployment](/build/deployments), not by editing the sender directly. | A workspace can hold several senders. A sender belongs to exactly one workspace — if the number is already connected somewhere else you get *"This WhatsApp number is already connected to another workspace."* rather than a silent takeover. ## Before you begin You need all four. Missing any one of them stops the flow partway through, usually inside Meta's window where the error is least helpful. - Admin access to your Meta Business portfolio - A business phone number you control - Access to receive Meta's SMS or voice verification on that number - An Amomic-AI agent, published, ready to handle conversations ::: warning Publish the agent first The connection flow asks which agent will answer before it opens Meta. If you have no agent yet the console says *"Create an agent before connecting WhatsApp."* and stops. Build and publish one first — [Quickstart](/start/quickstart) gets you there in a few minutes. ::: ## Connect a number Setup runs through Meta's Embedded Signup. You authorize inside Meta's own window; the credential comes back to us server-side. You are never asked to paste a token into your browser on the recommended path. ::: steps ::: step Choose the agent that will answer Open **Channels → WhatsApp**. On the first-sender card, pick the agent from **Choose the agent that will answer**. This is changeable later, per sender, from the **Automation** tab. ::: ::: step Authorize your business with Meta Choose **Continue with Meta**. Meta opens in a secure window where you sign in, select an existing WABA and phone number, or create them on the spot. The window is Meta's, not ours. Two things go wrong there often enough to name: - **Browser privacy settings block it.** Strict tracking protection stops Meta's SDK from loading and you get *"Meta login could not be loaded. Check browser privacy settings."* Try a normal window in a mainstream browser. - **You close it early.** Cancelling before Meta finishes returns *"WhatsApp setup was cancelled before completion."* Nothing is saved; start again. When it completes, we verify with Meta that the WABA and number you picked are actually the ones you were authorized for, check the number is not already connected elsewhere, subscribe the number to inbound webhooks, and store the credential in a secrets vault. The credential is never returned to the browser. ::: ::: step Verify a real conversation The **Overview** tab has a **Verify a conversation** card. It exercises the real inbound session and the real outbound reply path, which is the only proof that matters. 1. From your phone, send any message to the connected number. 2. Enter that same phone number in the field. 3. Choose **Send verification reply**. If step 1 is skipped you get *"Couldn't send the reply. Message the business number first to open the 24-hour window."* That is not a bug — it is [the 24-hour window](/channels/whatsapp-window) doing its job, and it is worth understanding before you build anything on top of this channel. ::: ::: ## Sender health Each sender shows a health state. These are the exact labels, and each one names its own fix. | State | What it means | |---|---| | `Ready` | Messages can be received and answered by the assigned agent. | | `Connected` | Verified live with Meta — this number is receiving and answering messages. | | `Finishing setup` | The connection is saved, but Meta setup is not fully active yet. Usually the number's own verification with Meta is still pending. | | `Agent required` | Choose which agent should answer messages for this number. Inbound messages arrive and go nowhere until you do. | | `Reconnect required` | Meta authorization is missing or no longer valid. Use **Reconnect Meta** on the Connection tab. | | `Paused` | This sender is disabled and will not handle messages. | The **Readiness** card breaks the same judgement into its four parts — phone number registered with Meta, authorization available, webhook subscription active, agent assigned — so you can see which one is failing rather than guessing. There is also a live check that calls Meta while you watch: **Verify with Meta** on the Connection tab. It resolves the stored credential with no fallback and makes one real request, so a `Reconnect required` result here is definitive. ::: note Quality rating Meta scores each number's quality as `Green`, `Yellow` or `Red` based on how recipients react to your messages. A red rating surfaces a warning in the console: pause non-essential sends, review recent blocks and reports, and confirm every recipient opted in. Meta may reduce your messaging limits if quality stays low. ::: ## Multiple senders and the default Add more numbers with **Add sender**. Each one authorizes separately through Meta and gets its own isolated credential — a problem with one sender's token never touches another. Per sender, independently: - The agent that answers - Whether incoming messages are marked as read - Whether a typing indicator shows while the agent composes - The business profile customers see One sender in the workspace is the **workspace default**, marked with a `Default` pill and set from **Make workspace default** on the Connection tab. The default matters in exactly one place: when [`POST /messages`](/api/messages) is called without a `sender_id`, the message goes out from the default sender. ```json title="Choosing a sender explicitly" { "to": "+919876543210", "sender_id": "was_9f2c41ab77de1063c8a4b512", "text": "Your technician is on the way." } ``` Call [`GET /channels`](/api/discovery) from your backend to list the senders your key can send from, with their ids — there is no other way to get a `sender_id` into code without copying it out of the console by hand. ::: warning Opt-outs are workspace-wide, senders are not A customer opt-out blocks sends from every number in the workspace. Everything else — agent, profile, templates, credential — is per sender. Do not assume a second number gives you a second chance with someone who unsubscribed. ::: ## The display name is Meta's decision The name customers see above your number is set in WhatsApp Manager and reviewed by Meta. It is read-only in the Amomic-AI console, with a padlock, because we genuinely cannot change it on your behalf — Meta reviews every display-name change. The console reflects what Meta reports: - *"A change to this name is awaiting Meta's review."* - *"Meta declined this name. Submit a different one in WhatsApp Manager."* - *"No display name yet. Set one in WhatsApp Manager — customers see it above your number."* Everything else on the business profile *is* editable here, and changes go straight to Meta and are live immediately: the About line (up to 139 characters), the description (up to 512), contact email, category, up to two websites, address, and the profile photo (square JPEG or PNG, at least 192×192, under 5 MB). ## Putting an agent on a sender Two routes reach the same place. **From the channel.** Open the sender's **Automation** tab, pick the agent under **Assigned agent**, and save. The agent's instructions and knowledge base power replies from that number. **From the agent.** Deploy the agent to the WhatsApp connection. Activating the deployment writes the binding onto the sender. Either way, the binding is owned by the deployment. That has two consequences worth knowing: - **Pausing a deployment unassigns the sender.** Inbound messages then arrive and are dropped, because no agent is bound. The sender shows `Agent required`. - **You cannot edit a live deployment.** Change something and you clone it, validate the clone, and activate — which transactionally replaces the old one. See [Deployments](/build/deployments). ## How an inbound message reaches your agent ``` Customer sends a message → Meta delivers a signed webhook to Amomic-AI → the number it was sent to identifies the sender → the sender identifies the workspace and the bound agent → the agent answers, grounded in its knowledge base → the reply goes back out on the same number ``` Three details in that chain surprise people: - **Routing is by the receiving number, not by the customer.** The same customer messaging two of your numbers gets two independent conversations with two independently assigned agents. - **An unrecognised number is dropped.** We acknowledge the delivery to Meta and log it, rather than leaving Meta retrying into a routing void. - **A sender with no agent is silent.** Not an error to the customer, not a fallback message — nothing. Check the `Agent required` state if messages seem to vanish. Every inbound message also restarts [the 24-hour window](/channels/whatsapp-window) for that customer on that sender. Read that page before you design any notification flow. ### What the agent can read | Customer sends | What happens | |---|---| | Text | Answered normally. | | Image (JPEG, PNG, WEBP, GIF) | Read by the agent. A caption is treated as the question; an image with no caption is answered on its own. | | Button tap or list selection | Treated as the customer's reply. | | Location | Passed through as the shared coordinates. | | Voice note, video, document, sticker | Not processed. The agent replies: *"I can read text and images right now — voice notes and other files aren't supported yet. Please type your question or send a photo and I'll help!"* | ### What the agent can send Text and interactive replies. The agent writes in WhatsApp's own formatting — `*bold*`, `_italic_`, `~strike~` — never markdown, and long replies split at sentence and paragraph boundaries rather than being truncated. It can also produce native WhatsApp UI: - **Up to 3 options** render as tap buttons, labels trimmed to 20 characters. - **4 to 10 options** render as a tap-to-open list, row titles trimmed to 24 characters. - **A single link** renders as a call-to-action button. Outbound images, documents, audio and video are not supported on this channel. If a customer needs a file, send them a link. ## Opt-outs WhatsApp opt-out is handled for you, before the message reaches the agent. A customer whose message matches an opt-out keyword exactly — after trimming whitespace and ignoring case — is suppressed immediately. **Opt-out keywords:** `stop`, `unsubscribe`, `opt out`, `optout`, `cancel`, `band karo`, `बंद करो`, `रोको`, `बंद` They get one reply and nothing else: > You won't receive any more messages from us. Send START to resubscribe. **Opt-in keywords:** `start`, `subscribe`, `unstop`, `shuru karo`, `शुरू करो` > Welcome back! How can I help you today? ::: danger Suppression is permanent until they send START There is no expiry, no cool-off, no per-campaign scope, and no console override. A suppressed recipient stays suppressed across every sender in the workspace until they message one of your numbers with an opt-in keyword themselves. Any send to a suppressed recipient — free text or template, from the API or from an agent — fails with `403 permission_denied`, message *"Recipient has opted out."*, and `details.reason` of `"suppressed"`. Handle that code in your integration and stop retrying; a retry cannot succeed. ::: Match is exact, not fuzzy. "please stop messaging me" does not trigger suppression — it goes to the agent as an ordinary message. If you want stricter handling than Meta's keyword convention, keep your own suppression list and check it before you call the API. ## Sending from your backend Once a sender is connected and healthy, [`POST /messages`](/api/messages) is the whole surface: ```bash curl -X POST https://services.amomic.in/api/v1/messages \ -H "Authorization: Bearer $AMOMIC_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-4821-shipped" \ -d '{ "to": "+919876543210", "template": { "name": "order_shipped", "language": "en", "variables": ["Asha", "AM-4821"] } }' ``` Two constraints decide the shape of every integration built on this channel: - **Free-form `text` only works inside the 24-hour window.** Outside it you get `409` with `session_window_closed`. See [The 24-hour window](/channels/whatsapp-window). - **Templates must already be approved by Meta.** The API sends them; it does not create them. See [Templates](/channels/whatsapp-templates). ## Troubleshooting ::: details Messages arrive but the agent never replies Check the sender's health. `Agent required` means nothing is bound — assign an agent on the Automation tab, or resume the paused deployment that used to hold the binding. Inbound is working fine in this state, which is exactly why it looks like the agent is ignoring people. ::: ::: details Sends fail with 412 channel_not_connected No WhatsApp sender is connected to the workspace at all, or the one you named in `sender_id` does not exist. Call [`GET /channels`](/api/discovery) to see what your key can actually send from. ::: ::: details The sender flipped to Reconnect required The stored credential no longer works — Meta revoked it, the authorizing admin lost access to the business portfolio, or the number was moved. Use **Reconnect Meta** on the Connection tab and re-authorize. Inbound stops until you do. ::: ::: details A number connected fine but shows Finishing setup Meta has not finished verifying the number itself. Complete verification in WhatsApp Manager; the state resolves on the next live check. ::: ## Next ::: cards ::: card The 24-hour window [The single WhatsApp rule](/channels/whatsapp-window) that decides whether your message sends or returns a 409. ::: ::: card Templates [Write, submit and send](/channels/whatsapp-templates) the approved messages that reach customers outside the window. ::: ::: card Send from code [POST /messages](/api/messages) — the full request contract, every error, and the idempotency rules. ::: ::: card Receive replies [Webhooks](/webhooks/overview) push `message.received` and `message.status` to your backend instead of you polling. ::: ::: ---------------------------------------------------------------------------- ## The 24-hour window URL: https://docs.amomic.in/channels/whatsapp-window ---------------------------------------------------------------------------- Everything that goes wrong on WhatsApp goes wrong here. If you read one page about this channel, read this one. WhatsApp does not let a business message whoever it wants whenever it wants. You may write freely to a customer only in the 24 hours after *they* last wrote to you. Outside that window you may send only messages Meta has already reviewed and approved — templates. There is no third option. Amomic-AI enforces this centrally, on the send path, so no caller can route around it: not the API, not an agent, not the console. ## The window in one picture ```mock {"component": "WindowDiagram", "props": {"caption": "The window opens on the customer's message and closes 24 hours later. Your replies do not extend it — only theirs do."}} ``` ## What opens it **A message from the customer. Only that.** Every inbound message from that customer to that sender restarts the 24-hour clock from the moment it arrives. A customer who writes to you every day keeps the window permanently open. A customer who wrote once last Tuesday is long outside it. Things that do **not** open or extend the window: - Your agent replying. Outbound never extends the clock. - Sending an approved template. It reaches them, but it does not open a free-form window on its own. - A delivery or read receipt. - The customer opening the chat, or the message being marked read. The window is scoped **per customer, per sender**. If a customer messages one of your numbers, you have an open window on that number and no window on the others. ## What you can send, when | Window | Free-form `text` | Approved `template` | |---|---|---| | **Open** — they messaged within 24 hours | Yes | Yes | | **Closed** — they have not, or never did | No — `409` | Yes | Templates deliberately bypass the window check. That is the entire reason they exist: they are the sanctioned way to start a conversation with someone who is not currently talking to you. ::: warning What templates do not bypass Opt-outs. A suppressed recipient rejects templates exactly as hard as free text — `403 permission_denied` with `details.reason` of `"suppressed"`. See [opt-outs](/channels/whatsapp#opt-outs). ::: ## The error, exactly Send free-form text to a closed window and you get this, every time: ```responses [ {"status": 409, "code": "conflict", "reason": "session_window_closed", "description": "The 24-hour session window is closed; only approved template messages can be sent."} ] ``` ```json title="409 Conflict" { "error": { "code": "conflict", "message": "The 24-hour session window is closed; only approved template messages can be sent.", "details": { "reason": "session_window_closed" }, "request_id": "req_01JQRS4T8XZ0AB1CD2EF3GH4" } } ``` Branch on `error.details.reason`, not on the message string. `conflict` covers more than one situation across the API; `session_window_closed` is unambiguous. ```javascript title="Handling it properly" const response = await fetch("https://services.amomic.in/api/v1/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.AMOMIC_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": `order-${order.id}-shipped`, }, body: JSON.stringify({ to: order.customerPhone, text: message }), }); if (response.status === 409) { const { error } = await response.json(); if (error.details?.reason === "session_window_closed") { // Not retryable as free text. Fall back to the approved template. return sendTemplate(order); } } ``` ::: danger A 409 here is not transient Retrying the same free-form send will fail identically until the customer writes to you again — which may be never. Backoff does not help. Queueing does not help. The only correct handling is to send a template instead, or to not send at all. ::: ## Nothing is sent automatically when the window closes There is no hold-and-release. There is no automatic re-engagement template. There is no queue that drains when the customer next writes in. When the window closes, the closed window is simply the state of the world. If you have something to say, you send a template. If you had a message pending in your own system, it is your system's job to notice the 409 and decide what happens next. We are explicit about this because the alternative — quietly substituting a template you did not choose, on a Meta conversation category you did not budget for, to a recipient who may not want it — is worse than an error. ## The consequence for your design Work through the timing of a real notification and the conclusion is unavoidable. A customer orders at 11 a.m. and asks a question in chat; the window opens. The order ships at 9 p.m. the next day — 34 hours later. The window closed 10 hours ago. Your "your order has shipped" message is a template, or it does not go out. Now generalise. **Anything triggered by your system rather than by the customer is almost always outside the window**, because your systems run on your schedule and customers write on theirs: | Flow | Triggered by | Realistically | |---|---|---| | Answering a support question | The customer, just now | Inside the window | | Agent follow-up during a live chat | The customer, minutes ago | Inside the window | | Order shipped / out for delivery | Your warehouse | Outside | | Appointment reminder for tomorrow | A scheduler | Outside | | Payment receipt, renewal notice | Your billing job | Outside | | Feedback request a day later | A cron | Outside, by definition | | Re-engaging a lapsed customer | A report | Far outside | So: **build every notification flow on templates from the start.** Not as a fallback bolted on after the first 409 in production — as the primary path. The pattern that survives contact with reality: ::: steps ::: step Get the template approved before you write the code Template review takes minutes to a day. Doing it after your integration is built means your launch waits on Meta. See [Templates](/channels/whatsapp-templates). ::: ::: step Send the template unconditionally for anything system-triggered Do not probe the window first. There is no endpoint that reports window state, and a template send works whether the window is open or closed. Checking would only add a failure mode. ::: ::: step Use free-form text only inside a conversation you are already in An agent replying to a live conversation is inside the window by construction — the customer's message is what woke it up. That is where free text belongs. ::: ::: step Treat 409 / `session_window_closed` as "switch to a template", not as "retry later" One code path, one decision, no queue. ::: ::: ## What the agent does when it hits a closed window An agent can be given the ability to send a WhatsApp message — following up a web chat, for example. When it tries and the window is closed, it does not silently invent a substitute or apologise vaguely. The tool returns a structured result the model can act on, using these exact values: `sent` · `template_required` · `needs_setup` · `opted_out` · `template_not_allowed` · `template_not_found` · `needs_information` · `not_configured` · `failed` `template_required` means the window is closed and the agent should reach for one of the templates it is permitted to send. Which templates those are is set on the agent's tool policy — the model cannot name a template you have not allowed. See [Tools & actions](/build/tools). ## Verifying the window is doing what you think The console's **Verify a conversation** card on the WhatsApp Overview tab is a window test in disguise: 1. Message your business number from your phone. The window opens. 2. Enter that number in the card and choose **Send verification reply**. 3. A reply arrives on your phone. Skip step 1 and the reply fails with *"Couldn't send the reply. Message the business number first to open the 24-hour window."* That is the window rejecting a free-form send, end to end, in ten seconds — a cheaper way to see the behaviour than discovering it in production. ## Common misreadings ::: details "My template send opened the window, so now I can send text" It did not. A template reaches the customer; only a message *from* the customer opens the window. If they reply to your template, that reply opens it. ::: ::: details "The customer read my message, so the window is open" Read receipts are not inbound messages. Nothing about the customer's behaviour inside WhatsApp opens the window except sending you something. ::: ::: details "It worked in testing and fails in production" In testing you were messaging the number yourself minutes earlier, so every window was open. Production traffic is triggered hours later by your own systems. This is the single most common way this rule is discovered. ::: ::: details "We have a second number, so we can message from that one" The window is per sender, so the second number's window is also closed — you have gained nothing except a second closed window. Opt-outs, meanwhile, *are* shared across senders, so the second number does not help there either. ::: ::: details "Can I query whether the window is open?" No. There is no endpoint that reports window state. Send the template. ::: ## Next ::: cards ::: card Templates [How to write and submit one](/channels/whatsapp-templates), what Meta approves, and how to fill its variables at send time. ::: ::: card POST /messages [The send contract](/api/messages) — text, templates, sender selection, and every error code. ::: ::: card Order-shipped update [A complete flow](/recipes/order-updates) built the right way round, template first. ::: ::: card Errors [The full error model](/api/errors), including how `code` and `details.reason` divide up. ::: ::: ---------------------------------------------------------------------------- ## Templates URL: https://docs.amomic.in/channels/whatsapp-templates ---------------------------------------------------------------------------- A template is a message you write in advance, Meta reviews, and you then send with the changeable parts filled in. It is the only way to reach a customer outside [the 24-hour window](/channels/whatsapp-window) — which, for anything your own systems trigger, is nearly always. Meta requires approval because templates are how a business can message someone who is not currently talking to it. Review is the control on that. It is not a formality: a template that reads like unsolicited promotion in a category that claims otherwise gets rejected, and templates that provoke blocks pull down the number's quality rating. ```mock {"component": "TemplatesScreen", "props": {"caption": "Templates in the console, with the raw status each one carries at Meta."}} ``` ## Anatomy A template has one required part and three optional ones. | Component | Required | Limit | Notes | |---|---|---|---| | **Header** | No | 60 characters | A short bold line above the message. Text only. | | **Body** | **Yes** | 1024 characters | The message. Where variables normally live. | | **Footer** | No | 60 characters | Small print. **No variables allowed here.** | | **Buttons** | No | 10 | Quick replies, links, or a call button. | Plus two identifiers that are not shown to customers: a **name** (lowercase letters, numbers and underscores, up to 128 characters) and a **language** code. ```mock {"component": "WhatsAppMock", "props": {"header": "Your order is on the way", "body": "Hi Asha, your order AM-4821 has shipped and arrives by 6 PM today. Track it any time with the button below.", "footer": "Reply STOP to opt out", "buttons": ["Track order", "Talk to support"], "time": "18:04", "caption": "The template order_shipped with {{1}} filled in as \"Asha\" and {{2}} as \"AM-4821\"."}} ``` ## Categories There are exactly three. Pick the one that describes what the message actually does — misclassifying is a common rejection reason, and marketing costs more than utility. | Value | Label | Use it for | |---|---|---| | `UTILITY` | Utility | Follows up on something the customer did — an order, a booking, an account change. | | `MARKETING` | Marketing | Promotions and announcements. Costs more and needs clearer opt-in. | | `AUTHENTICATION` | Authentication | One-time passcodes only. | ::: warning Create AUTHENTICATION templates in WhatsApp Manager The authentication category has rules of its own at Meta — fixed body structure, code-expiry handling, autofill behaviour — and the console composer does not implement them. If you need one-time-passcode templates, create them in WhatsApp Manager, then press **Sync** to pull them into the console so they can be sent. Everything on this page about sending applies to them normally; only authoring is elsewhere. ::: ## Status Templates carry Meta's verdict as a raw lowercase string. Three values matter: | Status | Meaning | |---|---| | `pending` | With Meta for review — usually minutes, sometimes a day. The status updates itself; you do not need to poll. | | `approved` | Ready to send. | | `rejected` | Meta won't deliver this. The rejection reason is shown in full on the template's detail dialog. | **Only `approved` templates can be sent.** Naming a `pending` or `rejected` one returns `404 not_found` with `details.reason` of `"template_not_found"` — the name exists in your workspace, but not as something sendable. ::: note Statuses you did not expect Meta occasionally reports states beyond these three, and they pass through lowercased rather than being forced into a bucket we would have to guess at. Treat anything that is not the literal string `approved` as not sendable. ::: ## Variables Variables are numbered placeholders: `{{1}}`, `{{2}}`, `{{3}}`. You write them into the body when you author the template, and supply the values at send time. ```text title="Body" Hi {{1}}, your order {{2}} has shipped and arrives by {{3}}. ``` Meta reviews the template with sample values filled in, so the composer asks for one sample per placeholder. Use realistic values — `Priya` and `AM-10428`, not `xxx` and `123`. A reviewer reading `Hi xxx, your order 123 has shipped` is being shown something that looks like spam. Rules the composer enforces before it will submit, because Meta rejects on all of them: - **A body cannot start or end on a placeholder.** *"The message can't start or end on a `{{n}}` — put words around it."* - **Two placeholders cannot be adjacent.** *"Two `{{n}}` next to each other reads as blank to a reviewer — separate them with words."* - **Numbering must be contiguous from 1.** *"Numbers must run 1, 2, 3… — `{{5}}` appears without `{{2}}`."* - **Every placeholder needs a sample.** - **Footers cannot contain placeholders.** ### Supplying values at send time Values are matched to placeholders **by position**. Two shapes are accepted: ```json title="Positional array" { "variables": ["Asha", "AM-4821", "6 PM today"] } ``` ```json title="Numbered object" { "variables": { "1": "Asha", "2": "AM-4821", "3": "6 PM today" } } ``` They mean the same thing. The array is less error-prone in code you will read again in six months; the object survives refactoring better when a template gains a placeholder. Header and button variables are supplied separately, with `header_variables` and `button_variables`, using the same two shapes. Passing the wrong number of values returns `400 invalid_argument` with `details.reason` of `"template_variables"`. Values are stripped of newlines and tabs and truncated at 1024 characters before they reach Meta — WhatsApp does not allow a placeholder to inject line breaks into a reviewed layout. ## Buttons Three button types exist. Nothing else. | Value | Console label | What it does | |---|---|---| | `QUICK_REPLY` | Quick reply | Sends its own label back to you as a message from the customer, which also opens the 24-hour window. | | `URL` | Visit website | Opens a link. The link may end in a single `{{1}}` so you can point at a specific order or booking. | | `PHONE_NUMBER` | Call phone number | Dials a number you specify, in full international form. | Limits, all enforced before submission: - **10 buttons maximum** on a template. - **WhatsApp shows three.** Past that it hides the rest behind a "See all options" control. Treat three as the practical design limit and 10 as the ceiling. - **At most 2 `URL` buttons.** - **At most 1 `PHONE_NUMBER` button.** - **Labels up to 25 characters**, no duplicates, no placeholders in labels. - **Quick replies must be grouped together** — Meta rejects them mixed in between link and call buttons. - A `URL` button's link must start with `http://` or `https://`, may end in one `{{1}}` and nothing else, and needs an example of the full link with the value filled in. ## Languages A template is identified by **name and language together**, not by an id. That pair is what Meta matches on when you send. The console offers: `en`, `en_US`, `en_GB`, `hi`, `mr`, `gu`, `ta`, `te`, `bn`, `kn`, `ml`, `pa`. At send time you may omit `language`, or name one that has no exact match. Resolution then tries, in order: an exact match, then the same base language (`en_US` falls back to `en`), then any `en*` variant, then the first approved template with that name. This means a send does not fail merely because you asked for `en_GB` and only `en` was approved. To offer a template in a second language, duplicate it, keep the name, and change the language. Same name plus same language is rejected as a duplicate. ## Writing one ::: steps ::: step Open the composer **Channels → WhatsApp → Templates → New template**. It is a single form, not a wizard. ::: ::: step Describe the message, or write it yourself The composer can draft the whole template from a plain-English description — *"Let customers know their order shipped, with a tracking link"* — including placeholder placement, sample values and category. If the agent you selected has a knowledge base, the draft is written from your actual business rather than generically. Whatever it produces is a starting point. Read every line before submitting; you are the one accountable for what Meta reviews. ::: ::: step Name it for the system that will send it `order_shipped`, `appointment_reminder_confirm`, `payment_receipt`. Lowercase, underscores, no spaces. Customers never see the name — your code does, and so does whoever debugs it. ::: ::: step Check the preview The preview renders both what the customer receives (samples filled in) and what Meta's reviewer reads (placeholders visible). Read the reviewer view. Most rejections are obvious from it. ::: ::: step Submit for review Choose **Submit for review**. The template appears as `pending`. Review usually takes a few minutes to a day, and the status updates itself. If the new template is not in the list immediately, press **Sync** — that pulls the current state of every template from Meta, including ones you created directly in WhatsApp Manager. ::: ::: ## Templates cannot be edited There is deliberately no edit control. An approved template is a thing Meta reviewed; changing its text would mean sending customers something that was never reviewed. To change one, open it and choose **Duplicate as new**: - **Change the name** to create a separate template. Both exist; you switch your code over when the new one is approved, then delete the old one. - **Keep the name and change the language** to add a language variant. This is also why deploying a template change is a two-step release: get the new template approved first, then point your code at it. Doing it in the other order means an outage between the code deploy and the approval. ::: danger Deleting removes every language variant Meta deletes templates by name, not by id. Deleting `order_shipped` removes the English, Hindi and Marathi versions together, and anything still sending that name starts failing with `template_not_found` immediately. There is no undo and no grace period. ::: ## Sending one ::: codegroup ```bash cURL curl -X POST https://services.amomic.in/api/v1/messages \ -H "Authorization: Bearer $AMOMIC_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-4821-shipped" \ -d '{ "to": "+919876543210", "template": { "name": "order_shipped", "language": "en", "variables": ["Asha", "AM-4821", "6 PM today"], "button_variables": ["AM-4821"] } }' ``` ```javascript Node.js const response = await fetch("https://services.amomic.in/api/v1/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.AMOMIC_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": `order-${order.id}-shipped`, }, body: JSON.stringify({ to: order.customerPhone, template: { name: "order_shipped", language: "en", variables: [order.customerName, order.reference, order.etaLabel], button_variables: [order.reference], }, }), }); if (!response.ok) { const { error } = await response.json(); throw new Error(`${error.code}/${error.details?.reason}: ${error.message}`); } ``` ```python Python import os, httpx response = httpx.post( "https://services.amomic.in/api/v1/messages", headers={ "Authorization": f"Bearer {os.environ['AMOMIC_API_KEY']}", "Idempotency-Key": f"order-{order.id}-shipped", }, json={ "to": order.customer_phone, "template": { "name": "order_shipped", "language": "en", "variables": [order.customer_name, order.reference, order.eta_label], "button_variables": [order.reference], }, }, ) response.raise_for_status() ``` ::: ```json title="201 Created" { "message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSMkI5RUE4...", "channel": "whatsapp", "to": "+919876543210", "status": "sent" } ``` `status: "sent"` means we accepted it and Meta took it. It is an acceptance acknowledgement, not a delivery receipt — delivery and read state arrive later on the `message.status` [webhook](/webhooks/events). Send the `Idempotency-Key`. A network timeout on a template send is exactly the case where a naive retry messages a customer twice. See [Idempotency](/api/idempotency). ## Errors you will actually hit ```responses [ {"status": 404, "code": "not_found", "reason": "template_not_found", "description": "No approved template with that name. Either the name is wrong, or the template is still `pending` or was `rejected`."}, {"status": 400, "code": "invalid_argument", "reason": "template_variables", "description": "Wrong number or shape of variables for that template's placeholders."}, {"status": 403, "code": "permission_denied", "reason": "suppressed", "description": "The recipient has opted out. Templates do not bypass suppression."}, {"status": 412, "code": "failed_precondition", "reason": "channel_not_connected", "description": "No WhatsApp sender is connected to this workspace."} ] ``` Note what is *not* in that list: there is no window error on a template send. Templates work whether the window is open or closed — that is their entire purpose. ## Practical guidance ::: tip One template per system event, named after the event `order_shipped`, `order_delivered`, `payment_received`. Resist the general-purpose template with six placeholders that any system can fill — it reviews badly, reads badly, and the day one caller passes the wrong argument order you have shipped nonsense to real customers. ::: - **Get templates approved before you write the integration.** Review time is not under your control. Building first means your launch date is Meta's decision. - **Put a `QUICK_REPLY` on anything you want a response to.** A tapped quick reply is an inbound message, so it opens the 24-hour window and your agent can take the conversation from there in free-form text. - **Keep the body under 1024 characters and well under it in practice.** Long templates read as promotional regardless of category. - **Do not put an opt-out line in the footer of a utility template** unless you want it there; the opt-out keywords work whether or not you advertise them. `Reply STOP to opt out` is standard on marketing and unnecessary noise on a delivery notification. - **Watch the quality rating** on the sender after you start sending at volume. Red quality means recipients are blocking or reporting you, and Meta reduces messaging limits if it stays that way. ## Next ::: cards ::: card The 24-hour window [Why templates exist](/channels/whatsapp-window) and when a free-form send is legal instead. ::: ::: card POST /messages [The complete send contract](/api/messages), including sender selection and metadata. ::: ::: card Order-shipped update [End to end](/recipes/order-updates) — template, idempotent send, and the webhook that confirms delivery. ::: ::: card Tools & actions [Let the agent send a template itself](/build/tools), restricted to the ones you allow. ::: ::: ---------------------------------------------------------------------------- ## How voice works URL: https://docs.amomic.in/channels/voice ---------------------------------------------------------------------------- A customer dials your number. It rings once, maybe twice, and your agent answers — the same agent that answers on your website and on WhatsApp, with the same knowledge base behind it. It speaks first, listens, answers, and hangs up when the conversation is done. Afterwards you get a transcript, an outcome, and response-time numbers for every turn. This page is about the inbound side: what the platform does with a call from the moment it arrives to the moment it finalises. Placing calls yourself is covered in [Outbound calls](/channels/outbound-calls). ## The path a call takes ``` Customer dials your number → the number identifies the workspace and the deployment on it → the deployment identifies the agent that answers → the call is answered → recording notice, if recording is on → the agent's opening line → the conversation → hangup, transcript written, outcome recorded ``` Every step depends on the one before it, and the first two are where nearly all setup problems live. ### Routing is by the number that was dialled The dialled number is the routing key. Not the caller, not the agent, not the workspace — the number. One number carries exactly one deployment, and that deployment names exactly one published agent version. Two numbers in the same workspace can answer with two completely different agents, in two different languages, with recording on for one and off for the other. Nothing about that is shared. This has a consequence worth internalising before you debug anything: **you cannot change what a number does by editing an agent.** Editing an agent produces a new draft. The number keeps answering with the version frozen into its live deployment until you publish and roll a new one. That is the whole point — see [Deployments](/build/deployments). ### Three ways a call does not reach an agent They look similar from the outside and are completely different underneath. | What happened | What the caller experiences | What you see | |---|---|---| | The number is not in your workspace at all | The call is refused before it is answered | Nothing. No call record, no charge — there is nothing to attach a record to | | The number is yours, but no agent is deployed to it | The call is refused before it is answered | Nothing, for the same reason | | Your workspace is billing-suspended | The call **is** answered, the agent speaks a short out-of-service line, then hangs up | A call record with outcome `suspended` | ::: warning A number with no deployment is silent, not broken A provisioned number that has never had an agent deployed to it refuses calls. Nobody hears an error message and nothing appears on the Calls page, which makes it look like the number was never provisioned. Check **Channels → Phone / Voice** — an owned number with no agent shows *"Ready to connect to an agent"* rather than *"Answered by …"*. Assigning an agent tenant-wide does not help either. A number must be explicitly deployed to. See [Numbers & KYC](/channels/phone-numbers). ::: ## The agent always speaks first There is no silent pickup and no "please state your query after the tone". The moment the call connects, the agent speaks: the recording notice if recording is enabled, then the opening line, and only then does it start listening. That ordering is deliberate. A caller who hears nothing for two seconds assumes the call failed and hangs up, and a recording notice that arrives after the caller has already said something is not a notice at all. The opening line is free text you set per number, up to 500 characters. Keep it to one or two sentences — it is spoken, not read, and a long greeting is a long wait before the caller can talk. If you leave it empty, a written-in fallback is used in the call's opening language; the English one is: > Hello, thank you for calling! I'm your virtual assistant — how may I help you today? You can also have the opening line drafted from the agent's knowledge base — **Generate with AI** in the voice settings writes one short line grounded in what the agent actually knows. It is a proposal: it appears in the field for you to read, edit, and save. Nothing is generated at call time, so the greeting a caller hears is always a string you approved. ::: note Outbound calls do not use this greeting "Thank you for calling" is wrong on a call you placed. Outbound calls open with a fixed template that names your business, discloses that the caller is an assistant, and asks whether now is a good time. See [Outbound calls](/channels/outbound-calls). ::: ## Settings live on the number, not on the workspace This is the single most common mental-model error on this channel. The greeting, the opening language, the call language mode, recording, and the maximum call length are all properties of **the deployment on one number**. Change them and you change what that number does. The number next to it is unaffected. A workspace-level set of the same values exists, and it is used only as a fallback for a number that has no value of its own. It is not a control panel for your voice channel — if you set the workspace greeting and the number has its own, the number wins, every time. Two practical consequences: - **Roll out changes per number.** Turning recording on for one number does not turn it on anywhere else. If you need it everywhere, do it everywhere. - **Saving voice settings rolls a new deployment.** The console tells you so: *"Saving rolls a new deployment onto this number so the change is auditable. Calls already in progress finish on the old settings."* A call in flight never changes language or greeting under the caller. The full field list, with limits, is in [Numbers & KYC](/channels/phone-numbers#settings-that-live-on-a-number). ## What a transcript contains Every finalised call carries a transcript: an ordered list of turns, each one a speaker, the text, and an offset from the start of the call. The Calls page renders it with `mm:ss` offsets, the caller labelled `Caller` and the agent labelled with its own name. ```mock {"component": "CallsScreen", "props": {"caption": "One call, fully expanded: outcome badge, response-time tiles, recording, and the turn-by-turn transcript."}} ``` Four things about transcripts that matter when you are auditing a call: **There are exactly two speakers.** Every turn is either the caller or the agent. There is no third role, no system speaker, and no separate channel for tool activity in the transcript itself. **Offsets are relative to the start of the call**, in milliseconds, not wall-clock timestamps. `00:14` means fourteen seconds after the call connected. Use `started_at` on the call record if you need absolute times. **An interrupted agent turn is recorded as what the caller actually heard**, with the suffix `—[interrupted]`. If the agent was three words into a sentence when the caller spoke over it, the transcript holds those three words and the marker — not the sentence the agent intended to say. Audio that was never played is never written down. This is what makes a transcript usable as evidence: it is a record of the call, not of the agent's intentions. **Spoken filler is in there too.** When the agent says something deterministic — a hold line while it looks something up, a check that you are still there, a goodbye — that line is written as an agent turn. It reads as slightly repetitive prose in the transcript, which is exactly what it sounded like on the phone. In the linked inbox conversation, those operational lines are separated out onto their own rail so they do not clutter the message history, and they are excluded from message counts. ### Response times, not per-turn latency Each call carries aggregate response-time statistics — the median, p95, and slowest turn, measured from the moment the caller stopped speaking to the moment the first audio came back. The console shows them under **Response performance** with a target of 1.5 seconds on the median. There are no per-turn latency numbers. If one answer felt slow, the slowest-turn figure tells you how slow the worst turn was, but not which turn it was. ::: note There is no automatic call summary The call record has a `summary` field and it is not populated. Do not build reporting on it. What you have is the transcript and the outcome; if you need a summary, generate one yourself from the transcript. ::: ## Recording Recording is **off by default**, on every number, and stays off until someone explicitly turns it on for that number's deployment. When it is on, the caller hears a notice before the greeting — before anything else on the call: > Just to let you know, this call may be recorded for quality and training purposes. On a Hindi or mixed-language call the same notice is spoken in the Hinglish register instead. ::: warning It is an announcement, not a consent gate The caller is told. There is no key to press to decline, no branch that continues without recording, and no way for the caller to opt out mid-call short of hanging up. If your obligations require a real opt-out, this feature does not provide one — and telling callers something is optional when it is not is worse than not telling them. Whether recording a given call is lawful is your decision to make, per jurisdiction and per use case, before you enable it. ::: The recording is a single audio file per call, produced by the platform rather than by the phone network. Caller and agent are on separate stereo channels — caller left, agent right — laid on one shared timeline with silence filled in, so the two sides line up and you can hear exactly where an interruption landed. Play recordings from the call detail pane on the Calls page. Playback links are minted on demand and expire after an hour, so a link you copied yesterday will not work today; open the call again. Over the API, `recording_uri` on [`GET /calls/{call_id}`](/api/calls) is a storage reference rather than a ready-to-play URL. ::: danger There is no automatic deletion No retention schedule runs against call recordings today. A recording you make stays until it is deliberately removed. Do not enable recording on the assumption that the platform will age the audio out for you, and factor that into whatever you tell callers and whatever your own policy says. ::: ## Call statuses `status` is where the call is in its lifecycle. These are the exact strings written to the call record and returned by the API. | Status | Meaning | |---|---| | `in_progress` | The call is connected and the conversation is happening. The Calls page badges this `Live`. | | `ringing` | An outbound call has been dialled and the destination handset is ringing. Nobody has answered. | | `dialing` | A bridged call — one where a member of your team is being connected to a customer — is being set up. | | `completed` | The call finalised. This includes outbound calls nobody answered, which finalise as `completed` with a duration of zero. | | `failed` | The call could not be set up, or the carrier refused to place it. | | `no_answer` | A bridged call that never connected both legs. | ::: warning `completed` does not mean the conversation happened An outbound call that rang out lands on `completed` with `duration_seconds: 0` and an outcome that tells you why. Always read `status` and `outcome` together — and if you are branching in code, read `outcome`. ::: ## Call outcomes `outcome` is the richer field: *why* the call ended. It is `null` while a call is live and set once at finalisation. | Outcome | What it means | Console label | |---|---|---| | `caller_hangup` | The caller ended the call. This is the ordinary ending for a conversation that ran its course, and the default when nothing more specific applies. | `Completed` | | `agent_hangup` | The agent decided the conversation was finished and ended the call itself — the customer said thanks and goodbye, and the agent took them at their word. | `Completed by agent` | | `idle` | The line went quiet. The agent checked whether the caller was still there, got nothing back, said goodbye and hung up. Usually a caller who walked away or a dead handset. | `Idle hangup` | | `max_duration` | The call hit the maximum length configured on that number and was ended. If you see this often, either the cap is too tight or the agent is not reaching a conclusion. | `Time cap` | | `suspended` | The workspace is billing-suspended. The call was answered only to say the service is unavailable, then ended. No agent ran. | — | | `setup_error` | The call was answered but the audio path failed to come up, so no conversation was possible. The caller heard silence and hung up. | `Setup failed` | | `originate_error` | The call was never placed — the carrier refused it at dial time. Outbound only. | — | | `hangup` | A bridged call in which both legs were connected ended normally. Not an AI conversation. | — | | `no_answer` | Nobody picked up, or the network ended the call for a reason we do not map more precisely. Outbound and bridged calls only. | — | Outcomes marked `—` have no badge of their own on the Calls page; those rows fall back to showing the call's status. The outcome filter on the Calls page is deliberately narrower than this list — it offers `All outcomes`, `Completed`, `Idle hangup`, `Time cap`, and `Failed`. To see everything, clear the filter and read the badges. ::: note Outbound network outcomes Calls you place through the API can also finalise as `busy`, `rejected`, `invalid_number`, `unreachable`, or `congestion`, depending on what the network reported. Those are documented alongside the endpoint in [Calls](/api/calls). ::: ### Which outcomes are your problem - `caller_hangup`, `agent_hangup` — nothing to do. These are calls that worked. - `idle` — worth sampling. A handful is normal. A spike usually means the agent asked something the caller could not answer, or a long silence while it looked something up. - `max_duration` — read the transcripts. A call that hits the cap is almost never a call that went well. - `setup_error`, `originate_error` — infrastructure. Not caused by the caller and not fixable in the agent's instructions. - `suspended` — billing. Every call is being answered with an out-of-service line; fix it before you look at anything else on this page. ## Finding a call Open **Calls** in the console. Every call, inbound and outbound, in one list, newest first. - **Search** by caller, by number, or by call id. - **Filter** by direction, by outcome, and by agent. - **Metric cards** across the top: `Loaded calls`, `Talk time`, `Completion`, `Median response`. They describe the calls currently loaded, not your account for all time — load older calls and they change. - **Select a call** for the full detail pane: the meta tiles (`Business number`, `Direction`, `Duration`, `Conversation turns`), response performance, the recording if there is one, and the transcript. From your own systems, read one call with [`GET /api/v1/calls/{call_id}`](/api/calls), or subscribe to `call.started` and `call.ended` [webhooks](/webhooks/events) and stop polling. There is no endpoint that lists calls — the console list is not backed by a public API, so keep the `call_id` values you create. ## Troubleshooting ::: details Callers say it rings and then cuts off No agent is deployed to that number, or the number is not in your workspace. Both refuse the call before answering, so there is no call record to look at — the absence of a record is the diagnosis. Check **Channels → Phone / Voice** for `Ready to connect to an agent`. ::: ::: details Every call ends immediately with outcome `suspended` The workspace is billing-suspended. Callers are hearing an out-of-service line. No agent runs, no greeting is spoken, and nothing you change on the number will help until billing is resolved. ::: ::: details The greeting is not the one I set You almost certainly set it in a different place than the one the call read. The number's own value wins over the workspace value. Open the voice settings for the deployment on *that number* and check the opening line there. If you edited the agent instead, remember that editing produces a draft — the number keeps running the version its live deployment froze until you publish and roll a new deployment. ::: ::: details The agent answered in the wrong language Two settings control this and they are not the same thing: the call language mode decides what the agent can follow, and the opening language decides which one it starts in. See [Languages](/channels/voice-languages). ::: ::: details Recording is on but there is no audio The call detail pane distinguishes the cases: *"Recording was enabled, but the audio is unavailable."* means recording was on for that call and the file did not survive. A call with recording off shows no recording section at all. Also check whether recording was turned on *after* the call — the setting applies from the next call onwards. ::: ::: details Response times are fine but the call felt slow Median and p95 measure the gap between the caller finishing and the first audio coming back. They do not measure how long the agent then talked for. A verbose agent feels slow with excellent latency numbers — tighten the instructions rather than chasing milliseconds. ::: ## Next ::: cards ::: card Get a number [Numbers & KYC](/channels/phone-numbers) — the compliance review, the request lifecycle, and every setting that lives on a number. ::: ::: card Languages [Call mode versus opening language](/channels/voice-languages), the eleven languages, and how to pick. ::: ::: card Place calls yourself [Outbound calls](/channels/outbound-calls) — what the customer hears first, and what you are responsible for. ::: ::: card Drive it from code [POST /calls](/api/calls) — place, read, and hang up calls over the REST API. ::: ::: ---------------------------------------------------------------------------- ## Numbers & KYC URL: https://docs.amomic.in/channels/phone-numbers ---------------------------------------------------------------------------- Phone numbers are regulated. You cannot click a button and have one thirty seconds later, and anyone offering you that in India is skipping a step you are legally on the hook for. Getting a number here is a three-part process: verify your business once, request a specific number and have it reviewed, then deploy an agent onto it. The first part takes a day or so. The second takes hours. The third takes a minute. Plan the first one into your timeline and the rest stops being a surprise. ```mock {"component": "PhoneScreen", "props": {"caption": "Channels → Phone / Voice: the numbers your workspace owns, which agent answers on each, and any request still in review."}} ``` ## One-time business verification Before your workspace can search for a number at all, it needs verified business identity on file. This is a **one-time step per workspace** — do it once and every number you request afterwards skips straight to number search. The console is blunt about why: *"Telecom regulations require verified business identity before a phone number can be issued."* ### What you submit Two text fields and two documents. Everything is required; there is no partial submission. | Field | What it is | |---|---| | **Legal name** | Your registered entity name, exactly as it appears on the registration document. Not your brand, not your trading name. | | **Alias** | A short name for this profile — usually your brand. This is the label, the legal name is the identity. | | **Registration certificate** | Certificate of Incorporation, or your Udhyam registration. | | **Tax proof** | GST certificate, or your business PAN. | Both documents: PDF, PNG or JPEG, up to 15 MB each. Both are validated before either is uploaded, so a bad second file does not leave you with a half-finished submission to clean up. ::: warning The legal name must match the document This is the single most common rejection. If the certificate says `Acme Technologies Private Limited`, submit that — not `Acme`, not `Acme Technologies Pvt Ltd`. A reviewer comparing two strings that do not match has no way to approve you, and a rejection restarts the clock. ::: ### The review, and what to expect A person reads your submission. That is the honest description — it is not an automated check that fails in three seconds, and it is not a queue that takes a fortnight. > Usually the same business day, up to 5 business days. Treat the second number as the one to plan around, especially if you submit late on a Friday. You are notified in the console and by email when it resolves. | Status | Console badge | What it means | |---|---|---| | `pending` | `Under review` | Submitted. Number search is still locked. | | `approved` | `Verified` | Number search is unlocked for the whole workspace, permanently. | | `rejected` | `Rejected` | Something did not check out. The reason is shown with the badge. | ::: note Resubmission starts the review fresh You can only resubmit from `Rejected`. A resubmission replaces the previous one entirely — new documents, new details, new review. There is no way to amend a submission that is already `Under review`; wait for it to resolve. ::: Until verification is approved, the request dialog blocks you with *"One-time verification required"* and sends you to **Settings → Compliance**. Nothing about number search is available before that. ## Requesting a number Once verification is approved, **Channels → Phone / Voice → Request number**. ::: steps ::: step Search for a number Pick a country and, if you care about the digits, a pattern — a prefix like `8069` narrows the results. Leave it empty to see whatever is available. Each result shows the number, its type, and where it is. If nothing matches, the pattern is usually the reason: *"Try a different pattern or leave it empty."* ::: ::: step Send one number for review Choose a number and confirm. The console states exactly what happens next, and it is worth reading rather than clicking past: - *"Your selection goes to the Amomic-AI team for review. No purchase happens yet."* - *"After approval, we purchase and configure it, then notify you here and by email."* - *"Open Manage number and connect it directly to the agent that should answer."* Nothing is bought at this point. Your request is a request. ::: ::: step Wait for provisioning After approval, the number is purchased, attached to the voice network, and registered to your workspace. Then it appears in your numbers list as `Active`. ::: ::: step Put an agent on it A provisioned number with no agent answers nothing. See [Assigning an agent](#assigning-an-agent) below. ::: ::: ### Request statuses A request moves through these states. The console badge and the copy underneath tell you which one you are in. | Status | Console badge | What it means | |---|---|---| | `pending_review` | `Under review` | Waiting on the Amomic-AI team. *"No purchase has been made. Our team is reviewing your selection."* | | `provisioning` | `Setting up` | Approved. The number is being purchased and configured. *"Approved — we're purchasing and configuring the number."* | | `provisioned` | — | Done. The number leaves the request list and appears as an owned number, `Active`. | | `rejected` | `Not approved` | A reviewer declined. The reason is shown, or *"Choose another number or contact support."* | | `failed` | `Needs attention` | Approved, but provisioning did not complete. *"Our team has been notified and will follow up."* You do not need to do anything. | Releasing a number runs through the same reviewed lifecycle in reverse: `releasing`, then `released`. ::: note There is no direct purchase Every number goes through review. There is no self-serve buy path, no API to purchase a number, and no way to skip the queue. If you already have a number provisioned on the Amomic-AI platform, the numbers card has an **Add number** disclosure for attaching it to this workspace. ::: ### Releasing a number **Manage → Request deletion**, which creates a reviewed request exactly like a purchase does. > Deletion is never immediate. It creates an admin request, and the number keeps routing calls until approval. That is intentional. A number that vanishes the instant someone clicks a button is a number your customers are still dialling. Calls continue normally while the request is reviewed, and the row shows `Deletion requested` and then `Deleting` while it moves. ## Assigning an agent A number and an agent are joined by a **deployment**, not by a setting on the number. Two routes reach the same place. **From the number.** **Manage** on the number, then **Answering agent**. Pick a published agent and save. The console notes that *"Reassignment takes effect on the next call"* — a call already in progress finishes with whoever answered it. **From the agent.** **Deploy → Phone**, then choose the number. Same result, written from the other side. Either way, three rules follow: - **One number, one agent.** There is no round-robin, no fallback agent, no time-of-day routing. If you need two agents, use two numbers. - **The agent must be published.** A draft cannot be deployed; the console blocks with *"Publish the agent before deploying it to a channel."* A deployment freezes a published version so it cannot change under your customers. - **Pausing the deployment unassigns the number.** The number stays yours and stays active, but it stops answering. Callers get a refused call and no call record appears — see [How voice works](/channels/voice). You can also unassign deliberately: the agent picker has a `No agent (unassign)` option. ## Settings that live on a number These are the runtime settings for one number, set on the deployment that binds an agent to it. They are per number. A second number in the same workspace has its own set and is unaffected by anything here. | Setting | Values | Default | |---|---|---| | **Opening line** | Free text, up to **500 characters**. Spoken the moment the call connects, before the caller says anything. Empty falls back to a written-in greeting in the call's opening language. | empty | | **Greet callers in** | One of the eleven supported languages, and it must be allowed by the call language mode. | Depends on the mode — see [Languages](/channels/voice-languages) | | **Call language** | `en`, `multi`, or `indic`. What the agent can understand and speak on this number. | `multi` | | **Record calls** | On or off. When on, callers hear a recording notice before the greeting. | **off** | | **Maximum call length** | **60 to 3600 seconds**. The call is ended when it hits the cap, with outcome `max_duration`. | **600 seconds** (10 minutes) | A few notes that save time later: - **The opening line counter says `{n}/500 · spoken in {language}`.** That second half is a live reminder of which language the line will be read in. Writing English text and setting the opening language to Hindi produces English words in a Hindi voice, which sounds exactly as bad as it reads. - **`Generate with AI`** drafts a single short opening line from the agent's knowledge base and puts it in the field. It is a suggestion — read it, edit it, save it. Nothing is generated during a call. - **The maximum call length is a safety net, not a target.** Ten minutes is generous for a support call and thin for a detailed booking. Set it high enough that a genuine conversation is never cut off, then look at how many calls end on `max_duration` — if the answer is more than a few, the agent is not reaching a conclusion and the cap is only hiding it. ::: note Saving rolls a new deployment The console says so directly: *"Saving rolls a new deployment onto this number so the change is auditable. Calls already in progress finish on the old settings."* A live deployment is immutable by design. Changing a setting clones it, validates the clone, and activates it in place of the old one, so there is a record of what the number was configured to do at any point in time. See [Deployments](/build/deployments). ::: ## Using a number as caller ID The numbers you own are also the numbers you can place calls from. When you call [`POST /api/v1/calls`](/api/calls), the `from_number` must be an active number in your workspace, and the agent that speaks is **the agent deployed to that number** — not the one named in `agent_id`. That means a number is a routing decision in both directions. Pick the caller ID and you have picked the agent. See [Outbound calls](/channels/outbound-calls). ## Troubleshooting ::: details Number search is greyed out Business verification is not approved. Check **Settings → Compliance** for the badge: `Under review` means wait, `Rejected` means fix and resubmit. ::: ::: details Verification was rejected and I do not know why The reason is shown next to the badge, and repeated in the email. The usual causes are a legal name that does not match the registration document, an expired or illegible document scan, or a tax proof that belongs to a different entity than the registration certificate. ::: ::: details The request has been `Under review` since yesterday Number requests are reviewed by a person, on business days. If it has been longer than the compliance SLA, contact support with the request open in front of you. ::: ::: details The number is `Active` but nobody can reach it No agent is deployed. An owned number with no agent shows *"Ready to connect to an agent"* instead of *"Answered by …"*, and refuses calls without producing a call record. ::: ::: details I deployed an agent but calls still use the old greeting Check which number you edited. Settings are per number, and the greeting you set on the workspace-level card is only a fallback for numbers that have none of their own. ::: ::: details Can I move a number to another workspace Not directly. A number belongs to one workspace. Release it through the reviewed deletion flow, then request it again from the other workspace — with no guarantee it is still available in between. ::: ## Next ::: cards ::: card How voice works [The inbound call path](/channels/voice), transcripts, recording, and every status and outcome. ::: ::: card Languages [Call mode and opening language](/channels/voice-languages) — the distinction that decides what your agent can follow. ::: ::: card Outbound calls [Calling customers](/channels/outbound-calls) from a number you own, and what you are responsible for. ::: ::: card Deployments [How a published agent gets onto a channel](/build/deployments), and why a live one cannot be edited in place. ::: ::: ---------------------------------------------------------------------------- ## Languages URL: https://docs.amomic.in/channels/voice-languages ---------------------------------------------------------------------------- Voice has two language settings and they do different jobs. Nearly every "the agent answered in the wrong language" report is someone who changed one and expected the other. - **Call mode** — which languages the agent is able to understand and speak on this number, for the whole call. - **Opening language** — which single language it says its first sentence in. The mode is the boundary. The opening language is a starting point inside that boundary. You must set both, and the opening language must be one the mode allows. ## Call mode Three values. The field is called **Call language** in the console; the values are exactly these strings. | Value | Console label | What the agent can follow | |---|---|---| | `en` | `English only` | English. Nothing else. | | `multi` | `Hindi + English` | Hindi and English, including callers who switch between them mid-sentence. | | `indic` | `All Indian languages` | All eleven languages below, and switches with the caller mid-call. | The console describes them the same way, and the descriptions are accurate rather than promotional: - `English only` — *"Highest transcription accuracy. Best when your callers speak English."* - `Hindi + English` — *"Natural Hinglish — handles callers switching mid-sentence. The usual choice for India."* - `All Indian languages` — *"Follows the caller across Hindi, Bengali, Marathi, Tamil, Telugu, Gujarati, Kannada, Malayalam, Punjabi and Odia — and switches with them mid-call."* Call mode is set per number, on the deployment that binds an agent to it. Two numbers in the same workspace can run in two different modes. See [Numbers & KYC](/channels/phone-numbers#settings-that-live-on-a-number). ## The eleven languages These are the language codes for the opening language. The names are the ones the console shows. | Code | Language | |---|---| | `EN` | English | | `HI` | Hindi | | `BN` | Bengali | | `MR` | Marathi | | `TA` | Tamil | | `TE` | Telugu | | `GU` | Gujarati | | `KN` | Kannada | | `ML` | Malayalam | | `PA` | Punjabi | | `OR` | Odia | The console renders each one as `English name · native script` — `Hindi · हिन्दी`, `Tamil · தமிழ்`, and so on. ## What each mode allows The mode gates the opening language. Pick a mode and the console greys out every language it does not allow. | Call mode | Opening language may be | Default | |---|---|---| | `en` | `EN` only | `EN` | | `multi` | `EN` or `HI` | `HI` | | `indic` | Any of the eleven | `HI` | Two things follow from that table. **`en` collapses the choice.** With English-only mode there is exactly one legal opening language, so the picker is disabled. That is not a bug in the console — there is nothing to pick. **`multi` defaults to Hindi, not English.** If you want a Hinglish-capable number that opens in English, set the opening language to `EN` explicitly. Leaving it alone gets you Hindi. If you try to save an impossible pair, you are stopped: > That opening language isn't available in this call language mode. The console explains the same thing in place, naming the way out: > Only English available on this call language. Choose "Hindi + English" or "All Indian languages" above to greet in another language. ::: note A stale combination is repaired, not fatal Settings get edited in two steps and out of order — someone narrows a number from `indic` to `en` while its opening language is still `TA`. That combination is nonsense, and it does not fail the call. At call time the mismatch is resolved to a language the mode does allow, and the call proceeds normally. A caller never hears a failure because of this. But the greeting they hear is not the one you configured, so if a number is opening in an unexpected language, check the mode before you check anything else. ::: ## Choosing a mode The right answer is usually `multi`, and the reasoning is worth having in full because the wrong choice is quiet — it does not error, it just degrades every call. ### `multi` — the right default for Indian support Real callers do not stay in one language. A caller opens in English, gets frustrated, switches to Hindi for the complaint, then reads out an order number in English again. `multi` is built for exactly that sentence and handles the switch without a beat. Choose it unless you have a specific reason not to. It is the mode most support lines in India should run in, and it is the platform default. ### `en` — when you genuinely only serve English Constraining the model to one language is the single biggest accuracy win available on this channel. There is no code-switching to detect and no ambiguity between two plausible transcriptions, so English names, addresses and reference codes come through more reliably. The condition is real, though: *genuinely* only English. If one caller in twenty speaks Hindi, `en` does not politely handle them — it transcribes their Hindi as garbled English and the agent answers the garbage. Pick `en` for an international product line or an internal desk where the language is a given, not because most of your callers speak English. ### `indic` — when your callers span regions A national helpline, a service with customers across states, a number printed on a product sold everywhere. `indic` lets the caller lead and follows them. The cost is breadth: recognising eleven languages is a harder problem than recognising two, and accuracy on any one of them is not what a narrower mode gives you. Do not choose `indic` "just in case" on a number whose callers are all in Delhi. Choose it when the spread is real. ::: tip One number per language community beats one number for everyone If you have both a Mumbai line and a Chennai line, two numbers — `multi` on one, `indic` on the other, or `indic` opening in `TA` — will outperform a single `indic` number for both. Mode and opening language are per number precisely so you can do this. ::: ## Switching language mid-call Inside whatever the mode allows, the agent follows the caller. If a `multi` call opens in Hindi and the caller answers in English, the agent continues in English. Switching requires **clear evidence** — a substantial stretch of speech in the other language, not one word. That threshold exists because the alternative is worse. A single stray token is enough for a recogniser to guess wrong, and an agent that flips language on one ambiguous word will spend the rest of the call in a language the caller never asked for. Being slightly slow to switch is recoverable. Switching on a false positive is not. Two consequences worth knowing: - **A one-word answer will not flip the call.** `"haan"` in an English call is not a request to switch to Hindi, and the agent treats it accordingly. - **A caller who wants to switch should say a sentence.** In practice they do; people who change language rarely do it one word at a time. ## Where to set it Both settings live in the same place, on the deployment that puts an agent on a number. ::: steps ::: step Open the number's voice settings From the agent, open the phone deployment and choose **Voice settings**. From the channel, **Channels → Phone / Voice → Manage** on the number. ::: ::: step Set the call language first **Call language** is the radio group at the top. Setting it first is not a style preference — it decides which options the language picker below it will even offer. ::: ::: step Then set the opening language **Greet callers in** lists the eleven languages with the disallowed ones greyed out. If the one you want is greyed out, the mode above is the reason. ::: ::: step Rewrite the opening line if you changed language The greeting is text you wrote; changing the opening language does not translate it. The counter under the field reads `{n}/500 · spoken in {language}` — check that the second half matches the language the text is actually in. ::: ::: step Save, then make a real call Saving rolls a new deployment onto the number, so the change is auditable and calls in progress finish on the old settings. Then dial the number yourself. A test call is thirty seconds and catches the greeting-in-the-wrong-language mistake before a customer does. ::: ::: There is also a workspace-level version of these settings on the **Phone / Voice** page. It is a fallback for numbers that carry no value of their own, not a master switch — a number's own setting always wins. If you are changing settings and hearing no difference, you are probably editing the fallback. ## Related language behaviour **The greeting is text, in the language you chose.** The opening line is free text you write. Nothing translates it. If the opening language is `TA` and your greeting is written in English, the call opens with English words spoken in the Tamil voice. The console shows `spoken in {language}` under the field for exactly this reason — write the greeting in the language you selected. **The recording notice has two registers.** When recording is on, the notice spoken before the greeting exists in English and in the Hindi/English register. An `indic` number uses the Hindi/English line rather than the caller's own language, because the notice is spoken before anyone has said anything and there is no caller language to match yet. See [How voice works](/channels/voice#recording). **Outbound opening lines have two variants.** The fixed opening on a call you place exists in English and in Hindi/English. An `indic` outbound number opens in the Hindi/English register. See [Outbound calls](/channels/outbound-calls). ## Troubleshooting ::: details The greeting language picker is disabled The call mode is `en`, which allows one opening language. Change the mode first, then the language. ::: ::: details I set the opening language and the call opened in a different one Either the mode did not allow it and the value was repaired at call time, or you set it on the workspace-level card while the number carries its own value. The number's value wins. ::: ::: details The agent understands the caller but replies in the wrong language Check the mode. On `en`, a Hindi caller is transcribed as approximate English and the agent replies in English to something the caller did not say. That is a mode problem, not an instruction problem — no amount of prompting fixes it. ::: ::: details Callers switch to Hindi and the agent stays in English It needs more than a word or two of evidence before switching. Read the transcript: if the caller genuinely spoke a full Hindi sentence and the agent stayed in English, check that the number is on `multi` or `indic` rather than `en`. ::: ::: details Can I force a call to stay in one language Yes — set the mode to `en`. There is no per-language lock inside `multi` or `indic`; following the caller is what those modes are for. ::: ## Next ::: cards ::: card How voice works [The call path end to end](/channels/voice) — routing, the opening line, transcripts, and outcomes. ::: ::: card Numbers & KYC [Where these settings live](/channels/phone-numbers#settings-that-live-on-a-number), and every other per-number option. ::: ::: card Outbound calls [What the customer hears first](/channels/outbound-calls) on a call you placed, in each register. ::: ::: ---------------------------------------------------------------------------- ## Outbound calls URL: https://docs.amomic.in/channels/outbound-calls ---------------------------------------------------------------------------- An outbound call is your agent picking up the phone and dialling someone. A payment failed on a delivery that is already moving, a ₹40,000 cart stalled at checkout, an appointment needs confirming today, a lead filled a form ninety seconds ago — moments where a message sits unread and a call does not. You trigger it from your own systems. The full request contract — every parameter, every error, the response shape — is in the [Calls API reference](/api/calls). This page is the other half: what actually happens on the phone, and what you are on the hook for. ::: warning This is a phone ringing in someone's pocket Everything on this page follows from that. A message that lands badly is ignored. A call that lands badly is an interruption, and at scale it is a complaint. Read the [responsible calling](#calling-responsibly) section before you ship anything that dials in a loop. ::: ## Which agent speaks The agent is resolved from **the number you call out on**, not from anything else in the request. Every number in your workspace has exactly one agent deployed to it. Pick the caller ID and you have picked the agent, its published version, its knowledge base, its instructions, its call language mode, and its time cap. There is no way to place a call with an agent that is not deployed to the number you are dialling from. That design is not a limitation to work around; it is what makes the call auditable. The customer sees a number, and that number always maps to one agent. If you need a different agent, call from a different number. The API's `agent_id` parameter does not select the agent. It asserts which one you expect, and the call is refused if it does not match what is actually deployed — a loud failure instead of the wrong agent quietly having the wrong conversation. Pass it for anything money-adjacent. [The details are in the API reference](/api/calls). ## What the customer hears first The agent speaks the moment the call connects. The opening is a **fixed template** — not your inbound greeting, and not generated at call time. ```text title="English" Hello, this is {agent name}, a virtual assistant calling on behalf of {company}. Is now a good time to talk? ``` ```text title="Hindi + English" Namaste, main {agent name} bol rahi hoon — {company} ki virtual assistant. Kya abhi baat karne ka sahi samay hai? ``` `{agent name}` is the agent's name, `{company}` is your company name as set on the agent. If either is missing you get a generic stand-in, which sounds exactly as vague as it looks — set both. Three things happen in that one sentence, in this order, and the order is the point: 1. **It names the business.** The person who answers knows within two seconds who is calling. Not a number they do not recognise; a company they have a relationship with. 2. **It discloses that the caller is a virtual assistant.** Before anything is asked of them, and without being asked. Not doing this is the difference between an AI call people tolerate and one they report. 3. **It asks whether now is a good time.** A real question, with a real answer. Someone in a meeting says no, and the call ends there. ::: note Your inbound greeting is deliberately discarded "Thank you for calling" is wrong on a call you placed, so the greeting configured on the number is not used for outbound. There is nothing to configure here — the opening is the same template for every outbound call, in the register the number's [call language mode](/channels/voice-languages) resolves to. A number running in `indic` mode opens in the Hindi/English register, because those are the two variants that exist. ::: ## How `purpose` shapes the call `purpose` is the errand. It is free text you send with the request, it reaches the agent as the reason for the call, and it stays in context for the whole conversation. Write it the way you would brief a colleague who is about to make the call for you — the situation, the outcome you want, and the fallback: ```json { "purpose": "Confirm Asha's dental appointment on Tuesday 3 October at 4pm, and offer to reschedule if it does not suit." } ``` ```json { "purpose": "Rohit's autopay for order AM-4821 failed this morning. Tell him the delivery is still scheduled, ask if he wants to pay by UPI link instead, and do not ask for card details on the call." } ``` Note the second one includes something the agent must *not* do. Purpose is the right place for that, because it is the only part of the call that knows about this specific customer. What `purpose` is not: - **It is not the greeting.** The opening line is fixed. Purpose shapes what the agent says after the customer answers the "is now a good time" question. - **It is not knowledge.** The agent still answers from the knowledge base bound to it. Facts a customer might ask about — your refund window, your delivery areas — belong in the knowledge base, where every call can use them. Purpose is for what is true about *this* call only. - **It is not a script.** The agent is not going to read it out. A purpose written as dialogue produces a stilted call. Purpose is stored on the call record, so a transcript months later still shows why the call was placed. ## The guards that protect you These run before anything is dialled, in order, and the first one that trips is what you get back. They exist because the failure modes they prevent are expensive. **Self-dial is rejected.** You cannot call one of your own numbers. Connecting the agent to the agent produces a conversation between two machines, on your minutes, that ends when one of them hits the time cap. **Caller ID must be yours.** The number you dial out on has to be an active number in your workspace. You cannot present someone else's number, and you cannot present a number you have released. Number spoofing is not a feature and is not achievable through a misconfiguration. **Ten concurrent calls per workspace.** A workspace runs at most ten live calls at once. Attempt the eleventh and it is refused with a rate-limit error naming concurrency as the reason — a distinct error from calling the API too fast, and it wants a different response from your code. Your loop is not too quick; your queue is draining faster than calls are ending. Wait for calls to finish rather than retrying immediately. This cap is also a useful blast radius. A bug in your trigger logic can waste ten simultaneous calls, not ten thousand. **A suspended workspace cannot dial.** Billing suspension stops outbound calling before any number is contacted. It is a configuration state, not a transient error — retrying will not clear it. The exact error codes, reasons, and `details` payloads for all of these are in the [API reference](/api/calls#failures). ::: tip Always send an Idempotency-Key A network timeout on a request that actually succeeded, retried without an idempotency key, is a second phone call to a real person about the same thing. Key it on the business event — `cart-88213-recovery`, not a random UUID — and a retry replays the original result instead of dialling again. See [Idempotency](/api/idempotency). ::: ## After you place the call The request returns while the phone is still ringing. Everything the customer experiences happens after your code has moved on. - **Nobody has answered yet.** A successful response means the carrier accepted the call. Duration, outcome, and timestamps are all empty by definition at that moment. - **Get the result from `call.ended`.** Subscribe to the [webhook](/webhooks/events) and you are pushed the outcome the instant the call finalises. Polling works, but it spends your API rate limit and tells you nothing until the call is over anyway. - **Unanswered calls still finalise.** A call that rings out ends with a zero duration and an outcome that says why — nobody answered, the line was busy, the number was invalid. Read the outcome, not the status. - **The transcript and recording behave exactly as they do inbound.** Same page, same detail pane, same rules. See [How voice works](/channels/voice). ## Designing the thing that decides to call The API call is the easy part. Almost every outbound problem originates in the system that decided to place it, so it is worth building that side deliberately. **Trigger on an event, not on a schedule.** "Call everyone in this segment" produces calls with no reason attached, and the person who answers can tell. "Call this customer because their payment failed eleven minutes ago" produces a call with an obvious purpose and a much better reception. **Make the decision idempotent before the request is.** The idempotency key protects you from a retried HTTP request. It does not protect you from two different jobs both deciding the same customer needs a call. Record "we called about this event" in your own database, and check it first. **Cap yourself below the platform cap.** Ten concurrent calls is the ceiling, not a target. If you are draining a queue, hold your own in-flight count somewhere lower and leave headroom, so a genuinely urgent call is not refused because a batch job filled every slot. **Decide what each ending means.** Map the outcome to a next action once, in one place, instead of scattering the logic: | Outcome | A reasonable next action | |---|---| | `caller_hangup`, `agent_hangup` | The call happened. Read the transcript if you need the content; otherwise close the task. | | `no_answer`, `busy` | They were unavailable. At most one retry, hours later, never the same minute. | | `rejected` | They actively declined. Treat it as a soft signal to stop, not as a reason to try again. | | `invalid_number` | The number is wrong. Fix the record — retrying cannot help. | | `unreachable`, `congestion` | A network problem. One retry later is fair. | | `idle` | Connected but nobody engaged. Do not immediately redial; look at a few transcripts first. | | `max_duration` | The conversation ran long without concluding. A human should look at this one. | | `setup_error`, `originate_error` | Your side and the customer both did nothing wrong. Retry once, then alert. | **Log the `call_id` against your own record at the moment you place the call**, not when it ends. If the process dies in between, the id is the only way back to what happened. ## Calling responsibly This section is not boilerplate. Outbound calling is the one thing on this platform that can do real damage to people who never asked to hear from you, and to your business through them. ### Get consent before you call Not implied consent, not "they are a customer so it is fine". A specific, recorded basis for calling this person about this subject: they requested a callback, they bought something and this call is about that purchase, they filled in a form that said you would call. If you cannot point at the moment they agreed, do not place the call. Keep that basis where your trigger logic can see it, so the check happens before the API call rather than after a complaint. ### Respect the time of day Use the customer's local time, not your server's. Do not call early in the morning, do not call late at night, and be more conservative than the legal minimum — the legal minimum is not a target. A call at 8:45 p.m. that is technically permitted still lands as an intrusion. **The platform does not enforce calling hours.** There is no quiet-hours window, no per-day cap per recipient, and no scheduling gate. If you place a call at 3 a.m., the call goes out at 3 a.m. ### Keep your own suppression list Someone who says "stop calling me" must stop being called — on every number, for every campaign, permanently, and the moment they say it. WhatsApp opt-outs are handled for you because WhatsApp has a keyword convention to hook into. **Voice has no equivalent.** A caller who tells your agent to stop calling has told your agent; nothing on the platform records that as a suppression, and the next job that queries your database will call them again. So build the list yourself: - Store an explicit do-not-call flag per contact and check it immediately before every call. - Feed it from every channel a person can use to say no — the call transcript, an email, a support ticket, someone on your team. - Make adding to it trivial and bypassing it impossible. A suppression list a campaign can skip is not a suppression list. - Never delete an entry to "re-permission" someone. ### Do not use this for cold bulk calling Placing calls to people who have no relationship with you and did not ask to hear from you is not what this channel is for. It generates complaints, gets numbers blocked, and puts your business on the wrong side of telecom regulation. There is no configuration that makes it acceptable. ::: danger Compliance is yours, and it is not optional Amomic-AI places the call you ask for. It does **not** check the destination against any do-not-call, do-not-disturb, or preference registry. It does not check DND. It does not enforce calling hours. It does not cap calls per recipient per day. There is no consent registry and no built-in opt-out mechanism on this channel. Compliance with Indian telecom regulation — including the DND registries, registration and scrubbing obligations, and the rules on unsolicited commercial communication — is **the caller's responsibility, which means yours**. Build those checks into the system that decides whom to call, before it ever reaches the API. Verify the current requirements with your own legal counsel; they change, and this page is documentation, not legal advice. ::: ## Next ::: cards ::: card The API reference [POST /calls](/api/calls) — parameters, responses, every error code, and the idempotency rules. ::: ::: card Trigger a call from your CRM [The full recipe](/recipes/crm-call) — trigger, idempotency, webhook, and writing the outcome back. ::: ::: card How voice works [Transcripts, recording, statuses and outcomes](/channels/voice) — identical for outbound and inbound. ::: ::: card Get a number to call from [Numbers & KYC](/channels/phone-numbers) — the caller ID is also the agent selector. ::: ::: ---------------------------------------------------------------------------- ## Install the widget URL: https://docs.amomic.in/channels/web-chat ---------------------------------------------------------------------------- The web widget is one line of HTML. There is no package to install, no build step, no framework plugin, and nothing to keep up to date — the script is served from our side and updates itself. ```html title="Paste before " ``` Replace the `data-bot` value with your own. You get it from the **Install** step of the web widget setup in the console — it always starts with `wgt_`. ```mock {"component": "WidgetMock", "props": {"caption": "The widget on a customer's screen. Colours, greeting, launcher and position all come from your configuration — the tag never changes."}} ``` ## Where it goes Immediately before the closing `` tag, on every page you want the widget to appear on. Usually that means your site's base template, footer partial, or layout component — somewhere it renders once per page rather than once per route. Two details: - **`async` matters.** It keeps the script off your page's critical path. The widget appears a moment after your content, which is the correct trade for a support widget. - **Include it once.** A second copy of the tag on the same page is ignored rather than producing two launchers, but there is no reason to rely on that. If you have both a site-wide layout and a per-page include, pick one. If `data-bot` is missing or empty, the script exits without doing anything — no launcher, no error in the console, nothing. A widget that silently fails to appear is worth checking for a typo'd or absent `data-bot` before anything else. ## Framework variants Every platform takes the same tag. Two have their own idiom worth using instead. ::: codegroup ```html HTML ``` ```jsx Next.js // app/layout.tsx — use next/script so Next controls when it loads. import Script from "next/script"; export default function RootLayout({ children }) { return ( {children}