# Agent actions Source: https://docs.nedzo.ai/agents/actions Configure actions your AI agent can perform during conversations, like booking meetings, transferring calls, sending messages, and calling external APIs. Actions let your agent do things during a conversation — book a meeting, transfer a call, send an email, post to Slack, or call an external API. Configure actions from the **Actions** tab. Each action has a **condition** that tells the agent when to use it. Write it in plain language, like *"When the contact wants to book an appointment"* or *"When the caller asks to speak with a manager"*. ## Calendar booking Book meetings on your connected calendar during a call or chat. **Setup:** 1. Connect a calendar provider in **Settings > Integrations** (Google Calendar, Calendly, Cal.com, or GoHighLevel) 2. Add a calendar action on the Actions tab 3. Set the condition, select the provider, and choose the calendar **Configuration:** | Field | Description | | - | - | | Name | Action display name | | Condition | When the agent should offer booking | | Calendar type | Google Calendar, Calendly, Cal.com, or LeadConnector | | Calendar ID | Which specific calendar to use | | Timezone | IANA timezone, or leave blank to let the AI ask the contact | The agent checks real-time availability and books directly on your calendar. The default booking flow (asking for the contact's timezone, offering a couple of slots at a time, collecting name + email only after a slot is selected) lives in your agent's prompt and is fully editable. See [Calendar booking instructions](/agents/calendar-booking-instructions) to customize it. ### When the agent can't book * **No calendar action.** The agent never offers to book. If a contact asks, it says booking isn't available and offers to take their details (name, number, and what they need) so your team can follow up. * **The calendar returns an error.** The agent follows the fix the error suggests, if there is one. If booking still fails, it tells the contact out loud instead of changing the subject. ## Call transfer Transfer a live call to a human or another phone number. **Transfer types:** | Type | Description | | - | - | | Warm | The agent introduces the caller before connecting them | | Cold | The agent connects the caller directly without introduction | **Configuration:** | Field | Description | | - | - | | Name | Transfer target name (e.g., "Sales Team") | | Condition | When to transfer (e.g., "Caller asks for a human") | | Transfer type | Warm or Cold | | Phone number | E.164 format destination number | | Transfer sentence | The exact sentence the agent speaks immediately before connecting the call. Use this to set the right language, tone, and any handoff context (e.g., *"Un momento, le paso con un agente."*). | The agent speaks the **Transfer sentence exactly as configured**, in whatever language you write it. There is no hard-coded English fallback — if you leave the field blank, the agent stays silent through the handoff. Set the sentence in the language your callers speak. ## Email Send an email during or after a conversation. **Requires:** A verified email domain in **Settings > Integrations > Email**. **Configuration:** | Field | Description | | - | - | | Name | Action name | | Condition | When to send | | From name | Sender display name | | From address | Email local part (domain comes from your email integration) | | Subject | Email subject (supports `{{contact.X}}` variables) | | Body | The email content. Supports `{{contact.X}}` variables and the dynamic variable picker. | | Recipient type | Contact's email, a specific address, or ask the contact | ### Confirming recipient on voice calls When the agent triggers Email during a **live voice call**, it does not send blindly. Before dispatching the email it confirms two things out loud with the caller: 1. **Recipient name** — who the email is for. If the contact record on file has a name, the agent confirms that name. If there is no contact yet, the agent collects the name from the caller. 2. **Destination email address** — the address to send to. The agent reads it back character-by-character (including domain) and asks the caller to confirm before sending. If the caller corrects either value, the agent uses the corrected value and asks for confirmation again. The email is only sent after explicit confirmation. This same flow runs whether the caller is an existing contact or a brand-new one — for new contacts, both the name and the email are collected during the call and saved on the contact record afterwards. ## Slack message Post a message to a Slack channel during a conversation. **Requires:** Slack connected in **Settings > Integrations > Slack**. **Configuration:** | Field | Description | | - | - | | Name | Action name | | Condition | When to send | | Channel | Which Slack channel to post to | | Message | Slack message body. Supports `{{contact.X}}` variables and the dynamic variable picker. | ## SMS Send a text message to a contact during a conversation. **Configuration:** | Field | Description | | - | - | | Name | Action name | | Condition | When to send | | From Phone Number | The number the text goes out from. The picker shows each number's friendly name next to the number. | | Message | SMS content. Supports `{{contact.X}}` variables and the dynamic variable picker. | ### Confirming recipient on voice calls When the agent triggers SMS during a **live voice call**, it confirms the recipient before sending: 1. **Recipient name** — who the SMS is for. If the contact has a name on file, the agent confirms it; if there is no contact yet, the agent asks for the name. 2. **Destination phone number** — the agent reads back the phone number digit-by-digit (including country code) and asks the caller to confirm before sending. If the caller corrects either value, the agent uses the new value and re-confirms. The SMS is only sent after explicit confirmation. For brand-new contacts, the name and phone collected during this flow are saved on the contact record afterwards. ## Custom action Call any external API during a conversation. Use this to look up data, update records, or trigger actions in systems that don't have a native integration. **Configuration:** | Field | Description | | - | - | | Name | Action name | | Condition | When to trigger | | Method | GET, POST, PUT, PATCH, or DELETE | | URL | The endpoint to call | | Parameters | Named parameters the agent collects from the conversation | | Auth type | None, Bearer token, API key, or Basic auth | | Headers | Custom HTTP headers | **How parameters work:** Define parameters with a name and description. The agent gathers the required information from the conversation and includes it in the API call. For example, a parameter named `order_number` with description *"The customer's order number"* tells the agent to ask for and extract that value. ### Request format When the action triggers, the voice engine sends an HTTP request to your configured URL. The request body contains a `message` object with the tool call details and the parameters the agent extracted from the conversation. **Example request:** ```json theme={null} POST https://your-api.com/check-order Content-Type: application/json Authorization: Bearer your-secret-token { "message": { "toolCallList": [ { "id": "test_call_id", "type": "function", "function": { "name": "check_order", "arguments": { "order_number": "ORD-12345", "customer_name": "John Doe" } } } ] } } ``` The `toolCallList` array contains one object per tool call. Each tool call includes: | Field | Type | Description | | - | - | - | | `id` | string | Unique identifier for this tool call | | `type` | string | Always `"function"` | | `function.name` | string | The action name (lowercased, spaces replaced with underscores) | | `function.arguments` | object | Key-value pairs where each key matches a parameter name you defined, and the value is what the agent extracted from the conversation | **Headers sent with the request:** | Auth type | Header added | | - | - | | Bearer token | `Authorization: Bearer {your token}` | | API key | `X-API-Key: {your key}` | | Basic auth | `Authorization: Basic {your credentials}` | Any custom headers you configured are also included. ### Response format Your endpoint must return a JSON response. The agent reads the response and uses it to continue the conversation. **Successful response (200):** ```json theme={null} { "results": [ { "toolCallId": "test_call_id", "result": "Order ORD-12345 is currently in transit and expected to arrive on January 20th." } ] } ``` The `results` array should contain one object with: | Field | Type | Description | | - | - | - | | `toolCallId` | string | The tool call ID from the request (echo it back) | | `result` | string | The information the agent should use in the conversation | The agent takes the `result` string and incorporates it into its response to the contact. Keep the result concise and factual — the agent will phrase it naturally. **Error response:** If something goes wrong, return an error message in the result. The agent will handle it gracefully: ```json theme={null} { "results": [ { "toolCallId": "test_call_id", "result": "Error: Order not found." } ] } ``` ### Timeouts and response limits Custom actions run synchronously — the conversation pauses while your endpoint responds. How long we wait depends on where the action runs: | Where the action runs | How long we wait | | - | - | | Voice calls | 10 or 30 seconds, depending on your account — see below | | Chat (web, SMS, WhatsApp, Instagram, Facebook) | 30 seconds | | The **Test** button in the action editor | 30 seconds | **Design voice actions to answer within 10 seconds.** On some accounts the voice limit is set by the telephony platform rather than by Nedzo, and there it can't be raised — an action that answers in 15 seconds will succeed in chat and in the Test panel and still time out on a live call. Building to 10 seconds is safe on every account. The action editor shows the exact limit for yours, under the endpoint URL. If your endpoint can't reliably answer inside the voice limit, respond immediately and finish the slow work afterwards — acknowledge the request, then notify the caller by SMS or email once it completes. Every second of waiting is silence on the call, so aim well under the limit rather than at it. Responses are capped at **4,000 characters** on every path. Anything longer is clipped, and the agent never sees the remainder. ## Managing actions * Click **Add** to create a new action * Use the kebab (`...`) menu on each action to **Edit**, **Duplicate**, or **Delete** it * Toggle **Active/Inactive** on each action to enable or disable it without deleting ### Duplicate Duplicating clones an existing action as a starting point for a new one. Available on every action type, but most useful for **custom actions** — most teams build a small set of similar custom actions (same auth, similar headers, similar URL patterns) and duplicating saves rewriting that boilerplate. The duplicated action: * Is created inactive — toggle it on once you've finished editing. * Gets a new name with " (copy)" appended; rename it before saving. * Copies every field: condition, method, URL, parameters, auth type, headers, and any provider-specific settings. * Is independent of the original — editing one does not affect the other. # Active hours Source: https://docs.nedzo.ai/agents/active-hours Set a weekly schedule for when your voice agent answers calls. Configure active hours, time zones, and behavior outside business hours. Set a weekly schedule for when your voice agent is available to take calls. Outside of active hours, calls won't be answered by the agent. Active hours apply to the calls a **voice agent** answers on its number. Configure it from the **Settings** tab under **Advanced Settings**. ## Setting up a schedule 1. Toggle **Schedule enabled** on 2. For each day of the week, set the hours your agent should be available 3. Toggle individual days on or off ### Time slots Each day can have one or more time slots: * Set a **From** and **To** time for each slot * Times are in 30-minute increments (e.g., 9:00 AM, 9:30 AM, 10:00 AM) * Click **Add time slot** to add multiple windows per day (e.g., morning and afternoon) * Remove a slot by clicking the **X** next to it ### Overnight schedules If your **To** time is earlier than your **From** time, Nedzo treats it as an overnight window. For example, 10:00 PM to 6:00 AM means the agent is active through the night. An **+overnight** label appears to confirm this. ## Copying schedules Instead of configuring each day manually, copy one day's schedule to others: 1. Set up the hours for one day 2. Click the **Copy** button on that day 3. Choose where to apply it: * Individual days (checkboxes) * All weekdays * Weekends * All days ## Outside active hours When a call comes in outside the scheduled hours, the agent won't pick up. You can pair this with your phone provider's settings to route calls to voicemail or another number during off-hours. # Calendar booking instructions Source: https://docs.nedzo.ai/agents/calendar-booking-instructions The booking flow your agent uses out of the box, and how to customize it. When you connect a calendar action to an agent, we add a default block of booking instructions to your agent's prompt. The agent uses these to ask for a timezone, present a couple of slots at a time, and collect the contact's name and email only after they've picked a time. You can edit these instructions directly in the prompt editor. They are part of your agent's prompt — they are not hidden from you and they are not enforced by the platform. Tune them, replace them, or remove them entirely. ## The default block Copy this into your agent's prompt if you ever need to restore the defaults. ```text theme={null} OPERATIONAL RULES: 1) Conversation Memory (CRITICAL): If the user's timezone, name, or email has already been provided earlier in this conversation, remember and reuse that information. NEVER re-ask for information already collected. 2) Timezones (REQUIRED FIRST): Before checking availability, ask the user for their timezone only if it has not been provided yet in this conversation. Always convert tool outputs to the user's local time when speaking. 3) Identity Verification (ONLY WHEN BOOKING): Only ask for Full Name and Email when the user has selected a slot and is ready to book. If name and email were already collected earlier in the conversation, skip this step and proceed directly to booking. Do NOT ask for name/email before showing availability. 4) Presenting Availability: Offer a maximum of two slots at a time. Use relative dates (e.g., "Tomorrow," "This Thursday"). Speak naturally, no bullet lists. BOOKING WORKFLOW: 1) Get Timezone only if not already known from this conversation 2) Check Availability with fetch_slots 3) Offer Slots (max 2 at a time) 4) Collect Contact Info only AFTER slot selection — if already known, confirm: "I'll book this under [name], [email] — shall I go ahead?" 5) Finalize with book ``` ## Why each rule exists * **Conversation Memory** — Without this, agents re-ask for timezone or email after every tool call, which feels broken to the contact. * **Timezones first** — Booking against the wrong timezone produces calendar invites the contact never sees. Asking up front is cheaper than apologizing later. * **Identity only at booking** — Asking for name/email before showing availability comes across as gating. Asking after a slot is selected feels like a normal confirmation step. * **Two slots at a time** — Voice channels can't read a list of ten options. Even on chat, two options at a time keeps the back-and-forth fast. * **Booking workflow ordering** — Codifies the sequence so the agent doesn't try to book before checking availability. ## Customizing You can edit any of these freely. Common tweaks: * **Offer more or fewer slots** — change "two slots" to "three slots" if you want a wider menu, or "one slot" for a more directive feel. * **Change the language** — these instructions affect tone. If your brand is formal, rewrite them as full sentences in that voice; the model will mirror it. * **Add booking-window rules** — e.g. "never offer slots within the next 4 hours" or "only weekday afternoons". * **Remove identity verification** — if you already authenticated the contact (e.g. logged-in web chat), delete rule 3 to stop the agent asking for name and email up front. On voice calls the agent still confirms the email before it books. See [Email when booking](#email-when-booking). ## Email when booking On voice calls, the agent books only with an email the caller said or confirmed on the call. * **Email on file.** The agent reads it back and asks if it is right. If the caller gives a different email, it books with that one. * **No email on file.** The agent asks for one. * **No made-up emails.** The agent never books with an address the caller did not give, such as `test@example.com`. It asks the caller instead. The agent does not say the meeting is booked until the booking succeeds. ## Phone numbers when booking Every booking needs the contact's **name** and **email**. The agent asks for whichever is missing, one at a time, name first. A **phone number** is only asked for when the calendar needs one. The agent does not ask for a phone on every booking. | Calendar | When the agent asks for a phone | | - | - | | Cal.com | Only when the event type has a required phone booking field | | Calendly | Only when the event's location is a phone call | | LeadConnector (GoHighLevel) | Never. The contact is created from the email. | * **Phone already on file.** The agent confirms it with the contact and uses it. * **No phone and the calendar doesn't need one.** The booking goes ahead without it. * **The calendar needs one.** The agent asks for it, saves it to the contact, and books again. It does not tell the contact the meeting is booked until the booking succeeds. ### Numbers outside the US The agent accepts numbers from any country. * **Local formats** such as `(888) 495-6788` are turned into full international format before booking. The country comes from your workspace country, then your organization's country, then the US. * **US 10-digit numbers** are saved with `+1`. The agent does not ask anything. * **Numbers that start with 0**, such as a UK `07911…` or a Philippine `0926…`, can't be placed without a country. The agent asks which country the contact is in, then saves the number with that country's code and without the leading 0. * **Other numbers it can't read.** The agent asks for the full number again, including the area code. * **If the calendar still rejects the number**, the agent reads it back and asks the contact to confirm it. The same rules apply on voice calls and in chat. ## Calendar timezone Each calendar action has an optional **Timezone** field (IANA, e.g. `America/New_York`). When set, the agent will not ask the contact for a timezone — it will book against the calendar's timezone instead. Leave it blank if your contacts span multiple timezones and the agent should always ask. ## Tool wiring (handled automatically) You don't need to add the tool names (`fetch_slots`, `book`, `cancel`, `reschedule`) or the calendar provider/ID to your prompt. Those are still injected automatically when you attach a calendar action — only the operational rules above live in your editable prompt. # Call analysis Source: https://docs.nedzo.ai/agents/call-analysis Set up automatic post-call analysis for Nedzo voice agents. Generate summaries and extract structured data from calls. After every voice call, Nedzo can automatically analyze the conversation. Configure these features from the **Settings** tab under **Conversation Analysis**. ## Summary generation Generate a concise summary of every call. When enabled, each call gets an automatic summary that includes: * Key discussion points * Outcomes and decisions * Action items * Any notable details Summaries appear on the call detail page in your dashboard. You can customize the summary prompt to focus on what matters to your business. Pick the shape of the summary under **Conversation Analysis**: | Format | What you get | | - | - | | **Summary with bullet points** | Up to 5 sentences, then up to 5 bullet points. This is the default. | | **Summary** | Up to 5 sentences only | | **Bullet points** | Up to 5 bullet points only | **Custom prompt example:** *"Summarize the call focusing on: the contact's main pain point, any pricing discussed, and the agreed next steps."* ## Outcome Every conversation gets one **Outcome**, on every channel: voice calls, web calls, chat, SMS, email and the other messaging channels. The list is fixed and the same for every workspace, so an outcome means the same thing on every agent, in Conversation Logs, and in workflow conditions. | Outcome | Description | | - | - | | Appointment booked | A meeting time was agreed and confirmed | | Booking failed | The agent tried to book, but the booking did not go through | | Transferred | The call was transferred after a conversation took place | | No answer | No person spoke on the call | | Failed | The call could not connect | | Voicemail | The call reached voicemail. Outbound calls only | | Follow up | The contact is still interested and needs a next step, such as more information or a quote | | Callback requested | The contact asked to be called back later | | Not interested | The contact declined | | Disqualified | The contact can be reached, but doesn't fit what the agent is calling about. For example, not the decision maker or outside the service area | | Do not disturb | The contact asked not to be contacted again | | Wrong number | Reached the wrong person or number | | Other | Nothing else fits, or the contact never took part | Nedzo sets **No answer**, **Failed**, **Appointment booked**, **Booking failed** and **Transferred** from what happened in the conversation. The AI reads the conversation to pick one of the others. When it can't decide, the outcome is **Other**. Only **Do not disturb** turns on Do Not Disturb (DND) for the contact. A plain **Not interested** does not. **Do not call** is now called **Do not disturb**. **Positive** and **Completed** are gone, and older conversations that had them now show **Other**. Outcome was previously called **Disposition**, and each agent had its own editable disposition prompt. That prompt is gone: the outcome list is owned by Nedzo and can't be customized. ## Contact details When a conversation ends, Nedzo fills the contact's **first name**, **last name**, **email**, **phone**, and **company** from what the contact said. This runs on voice, chat, web, and email conversations. You don't need an extraction field for these five. * A field is filled only when it is empty. * A value already on file is replaced only when the contact corrects it. * Nedzo never guesses. A value is saved only when the contact said it in the conversation. * A phone or email that another contact in your workspace already has is skipped. * Test calls and the chat simulator don't fill contact details. ## Data extraction Extract structured data from conversations and save it to your [custom fields](/concepts/contacts#custom-fields). This is useful for pulling out specific information that your team needs. ### Adding extraction fields 1. Click **Add** under Data Extraction 2. Configure the field: | Setting | Description | | - | - | | Save to contact field | The custom field the value is saved to. Type in the box to search your fields. | | Description | What the field captures (e.g., "The monthly budget the caller says they have to spend"). The AI reads this, finds the value in the conversation, and saves it to the field. | 3. The agent extracts this data from every conversation Nedzo sets the field's name and type from the custom field you pick. First name, last name, email, phone, and company are not in the **Save to contact field** list. Nedzo fills them for you (see [Contact details](#contact-details)). An older extraction field that saved to one of them now shows **Filled automatically**. ### Use cases * **Budget**: the contact's stated budget * **Decision timeline**: when they plan to make a decision * **Has authority**: whether they're the decision maker * **Pain points**: main challenges mentioned * **Competitor**: any competitor names mentioned Extracted data appears on the call detail page and can be used for filtering and reporting. ## Conversation Logs **Conversation Logs** lists one row per conversation, with its outcome and summary. Use the **Outcome** filter to show only conversations with a given outcome. For chat and email conversations, the **Replies** column counts the AI replies sent in that conversation. Replies from your team, sends that failed, and imported WhatsApp history don't count. Open a row to read the full thread. Long threads load in full, not only the newest messages. ### Agent tool calls Open a row to see the tools the agent ran in that conversation, such as booking a meeting or sending a message. Each one shows as a chip where it happened: * **Chat, web chat, SMS, email, WhatsApp, Instagram and Messenger**: under the agent reply that ran it, in the order it ran. * **Voice calls**: inside the transcript, after the spoken turn that led to it. Click a chip to see what the agent sent and what came back. Use it to check why the agent did something, or why an action failed. If the agent ran the same tool twice, you see two chips. Tool calls show only in Conversation Logs. Unibox does not show them, so your team's inbox stays about the conversation itself. Chips are not messages, so they don't change the **Replies** count. Calls placed from the dialer have no agent, so they show no tool calls. On a workspace with more than one [Instagram account](/integrations/instagram), Instagram rows name the account the conversation came through. # Compliance Source: https://docs.nedzo.ai/agents/compliance Turn on recording consent and AI disclosure per agent. Both are off by default and are spoken or sent at the start of a conversation in your agent's language. Nedzo can tell people they are dealing with an AI, and that a call is being recorded. Both are **off by default**. You turn each one on yourself, per agent, and nothing is ever forced on you based on where a contact is calling from. Find these in the agent's **Settings** tab, in the **Compliance** section. ## What you can turn on | Setting | Agents | What it does | | - | - | - | | **AI disclosure** | Voice, chat and web agents | Tells the person they are dealing with an AI assistant from your business | | **Recording consent** | Voice agents only | Tells the caller the call is being recorded | Recording consent is for voice agents only on purpose. There is nothing to consent to recording on chat or email. A chat agent's **AI disclosure** switch covers every channel that agent answers, email included. The wording follows the channel, as shown below. ## Turning them on 1. Open the agent and go to the **Settings** tab 2. Find the **Compliance** section 3. Turn on **AI disclosure**, and on a voice agent also **Recording consent** if you want it The switch shows whether each one is on. ## What the contact hears or reads The wording is fixed, so you do not have to write it. Your business name is filled in automatically. ### Phone The message plays at the very start of the call, before your agent's opening line. * **AI disclosure only:** "Please note that you're speaking with an AI assistant from your business." * **Recording consent only:** "Please note that this call is being recorded for quality and training purposes." * **Both on:** the two are combined into one sentence, so the caller hears a single preamble instead of two. ### Chat Sent as the first message of a new AI-handled conversation: "Please note that you're chatting with an AI assistant from your business." ### Email Included in the first reply: "Please note that this message was sent by an AI assistant from your business." ## Languages The text matches your agent's language. Eight are covered: English, Spanish, French, German, Portuguese, Dutch, Chinese, and Japanese. You do not need to translate anything. ## Testing it The **Test** preview shows the disclosure too, on phone, chat, and email. So you can turn a toggle on and hear or read exactly what a real contact would get before you go live. ## Things worth knowing * **Off by default.** An agent you never touch behaves exactly as it does today. * **Per agent.** Turning AI disclosure on for one agent does not turn it on for your other agents. * **Never forced.** Nedzo does not switch these on for you based on the caller's location. Whether you need them is your call. * **One preamble, not two.** With both phone settings on, the caller hears one combined sentence. If you operate in the EU, the AI Act (Article 50) expects people to be told when they are interacting with an AI. That covers phone, chat, and email, which is why AI disclosure is available on all three. Recording consent rules vary by country and by US state. Nedzo gives you the switches; which ones you need is a decision for you and your legal advisor. # Knowledge base Source: https://docs.nedzo.ai/agents/knowledge-base Add files, URLs, and text to your agent's knowledge base. The agent searches this content during conversations for accurate, context-aware answers. The knowledge base gives your agent access to information it can reference during conversations. When a contact asks a question, the agent searches the knowledge base for relevant content and uses it to respond accurately. Configure knowledge sources from the **Knowledge** tab. ## Source types ### Website Add a URL and Nedzo crawls the page content. Good for: * FAQ pages * Product documentation * Pricing pages * Company information If you entered your website during onboarding, it's already here — Nedzo ingests it as your first knowledge source, so you don't need to add it again. **Indexing status** — A newly added website shows an **Indexing** chip while Nedzo crawls it, then switches to indexed once the content is searchable. The chip updates on its own; you don't need to reload the page. Your agent can only answer from a source after it finishes indexing. ### File Upload documents for your agent to reference. Supported formats include PDFs and other common document types. Good for: * Product catalogs * Policy documents * Training materials * Price lists ### Text Paste text directly. Good for: * Quick notes or context * Frequently asked questions * Scripts and talking points * Temporary information ## Managing knowledge Click **Manage** on the Knowledge tab to open the knowledge panel. From here you can: * **Browse** all knowledge items in your workspace * **Search** by name or content * **Filter** by type (Website, File, Text) * **Link** existing knowledge items to your agent * **Unlink** items you no longer need on this agent * **Create** new knowledge items directly Knowledge items are shared across your workspace. One item can be linked to multiple agents, so you don't have to duplicate content. ## Correcting an answer When an agent answers a question badly, you can correct it on the spot and save the correction as knowledge. The next time someone asks, the agent uses your version. **From Unibox:** hover a message the AI sent and click **Improve message**. Write the answer you wanted and save it. **While testing an agent:** correct the answer directly in the test panel. Either way the correction is saved as a question-and-answer pair in your workspace knowledge, under **Improved questions**. Corrections carry higher retrieval priority than ordinary content, so a corrected answer wins over a page that says something older or vaguer. Corrections are shared across your workspace like any other knowledge item, so fixing an answer once fixes it everywhere that item is linked. ## How it works When a contact asks a question, the agent: 1. **Rewrites the query** against the last few turns of the conversation — so "how much is it?" becomes a query that includes what "it" actually refers to. This dramatically improves recall on follow-up questions. 2. **Pre-fetches** relevant passages from the linked knowledge base using semantic search **before** the LLM is called. The retrieved content is injected directly into the agent's context. 3. **Generates an accurate response** using the pre-fetched passages, plus any conversation history and the agent's prompt. 4. **Cites the information** naturally in the conversation — including numbered citations on chat / web replies (see [Source attribution](#source-attribution)). The agent only references knowledge you've linked to it — not knowledge from other agents in your workspace. ### Prefetch vs. on-demand lookup There are two retrieval modes: * **Prefetch (default)** — Knowledge is fetched before the model runs, on every contact turn that could benefit from it. This is the new default and is what you want in almost all cases — the model has the information up front, replies are faster, and citations are reliable. * **On-demand lookup tool** — In older agents, the model decides whether to call a `lookup_information` tool, then gets the result and replies. This is now **automatically suppressed** when prefetch returns useful results, to avoid a redundant second round-trip. If your agent has the lookup tool enabled and you want to keep its old behavior (always let the model decide), there is an opt-in override in the agent settings; most agents should leave this off. Prefetch covers all conversational channels: voice calls, SMS / WhatsApp / Email / Instagram / Messenger via chat agents, and Web Agents. ## Source attribution When the AI uses content from your knowledge base, responses include numbered superscript citations (¹, ², ³) inline with the text. A footer at the bottom of the message lists all cited sources. For **website** sources, each citation links directly to the original URL. Clicking a citation or footer entry opens the source page in a new tab. File uploads and text sources do not include clickable links since there is no URL to reference — they still show the citation number and source name in the footer. ## Tips * **Keep content focused** — Smaller, specific documents perform better than large general ones * **Update regularly** — Remove outdated information and add new content as your business changes * **Use descriptive names** — Name your knowledge items clearly so you can find them easily * **Test your agent** — After adding knowledge, test the agent to make sure it uses the information correctly # Agents overview Source: https://docs.nedzo.ai/agents/overview Build and configure AI agents in Nedzo for voice calls, chat, and web agents. Set up prompts, knowledge bases, actions, call analysis, and active hours. Agents are the core of Nedzo. They handle conversations with your contacts across voice calls, text messages, and web chat. Each agent has its own personality, instructions, voice, and capabilities. Voice, chat, and web agents System prompt, variables, tone, voice, and language Calendar booking, transfers, email, Slack, SMS, and webhooks Give your agent context from files, websites, and text Summaries, outcomes, and data extraction Schedule when agents accept calls # Post-conversation webhook Source: https://docs.nedzo.ai/agents/post-conversation-webhook Send conversation data to your server after a voice, chat, or web agent finishes. Includes the transcript, summary, and extracted fields. Every agent can fire an outbound webhook the moment a conversation ends. Use it to push transcripts, summaries, outcomes, and extracted fields into your own CRM, data warehouse, or notification system. Previously called the "Post-Call Webhook" (voice only). It now fires for all three agent types: | Agent type | When it fires | | - | - | | Voice | After a phone call ends (inbound or outbound) | | Chat | After an SMS, Instagram, Messenger, email, or web chat conversation is finalized | | Web Agent | After a web agent conversation (voice or chat) ends | ## Setup ### In the dashboard 1. Open your agent and go to the **Settings** tab. 2. Under **Post-Conversation Webhook**, paste your endpoint URL and save. 3. Click **Test webhook** to fire a sample payload so you can verify your server accepts it. ### Via API Set `postConversationWebhookUrl` when creating or updating an agent: ```bash theme={null} curl -X PATCH "https://api.nedzo.ai/v1/agents/{agentId}" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed"}' ``` Set the field to `null` to disable the webhook. The legacy field name `postCallWebhookUrl` is still accepted on create/update for backwards compatibility, but API responses always return `postConversationWebhookUrl`. ## Delivery semantics * **Fire-and-forget.** Nedzo sends the request but does not retry on failure. * **Timeout.** The request is aborted if your server takes longer than 10 seconds to respond. * **Success.** Any `2xx` status is logged as delivered. Non-2xx responses and timeouts are logged but do not retry. * **Order.** One webhook per conversation, sent after the conversation has been persisted, summary/extraction has run, and the workflow trigger has been queued. * **URL validation.** Only `https://` (or `http://` for public hosts in development) is allowed. Internal IPs and metadata endpoints are rejected to prevent SSRF. * **HIPAA.** If the agent has HIPAA compliance enabled, `transcript` is omitted from the payload and `summary` is sent as `null`. ## Payload Nedzo sends a `POST` request with `Content-Type: application/json` and a JSON body. ### Common fields Every payload includes these fields, regardless of channel: | Field | Type | Description | | - | - | - | | `event` | string | `"conversation.completed"` for chat and web. Voice sends `"call.completed"` for backwards compatibility. | | `channel` | string | One of `voice`, `web`, `sms`, `email`, `whatsapp`, `instagram`, `facebook`, `web_chat`. | | `conversationId` | string (UUID) | The Nedzo conversation record ID. | | `workspaceId` | string (UUID) | The workspace the agent belongs to. | | `startedAt` | string (ISO 8601) | When the conversation started. | | `endedAt` | string (ISO 8601) | When the conversation ended. | | `agent` | object | `{ agentId, name, phone? }`. Use `agent.agentId` to identify the agent. `phone` only included for voice. | | `contact` | object \| null | Contact record, or `null` when no contact was matched/created. Always a fully populated object when present — never an empty `{}`. Voice/web include `{ contactId, firstName, lastName, phone, email, contactBusinessName, tags, customFields }`; chat includes `{ contactId }`. Values in `customFields` are typed JSON primitives (see [Field-value typing](#field-value-typing)). | | `analysis.summary` | string \| null | AI-generated summary. `null` if HIPAA is enabled or summary generation failed. | | `analysis.outcome` | string \| null | The conversation's [Outcome](/agents/call-analysis#outcome), e.g. `Appointment booked`. Sent for every channel. `Do not call` is now sent as `Do not disturb`, and `Positive` and `Completed` are sent as `Other`. | | `analysis.disposition` | string \| null | **Deprecated.** Old name for `analysis.outcome`, always the same value. It will be removed in a future release, so read `analysis.outcome` instead. | | `analysis.dataExtraction` | object | Map of configured extraction field names to typed JSON values (`string` / `number` / `boolean` / `null`). See [Field-value typing](#field-value-typing). | | `transcript` | string | Full transcript, one conversation turn per line, separated by newlines (`\n`). For **text channels** (SMS, WhatsApp, Instagram, Facebook, web chat, email) each line is prefixed with a `[YYYY-MM-DD HH:mm:ss]` timestamp in your workspace timezone, e.g. `[2026-06-15 14:23:07] User: ...`. **Voice and web** transcripts are not timestamped and use the plain `Speaker: text` form. In both cases each line is `Speaker: text` (`User:` or `Assistant:`). The field name and string type are unchanged. **Omitted entirely when HIPAA compliance is enabled.** | ### Voice & Web Agent fields | Field | Type | Description | | - | - | - | | `direction` | string | `"inbound"` or `"outbound"`. | | `durationSeconds` | number | Call/session duration in seconds. | | `endedReason` | string \| null | Raw reason the call ended (e.g. `customer-ended-call`, `no-answer`, `voicemail`, `assistant-forwarded-call`). Use this for fine-grained branching. | | `agent.phone` | string \| null | The phone number the agent used (voice only). | | `appointmentDate` | string (ISO 8601) \| null | Confirmed appointment date/time if a calendar booking tool fired during the conversation. Resolved to UTC using the booking tool's configured timezone. `null` when no booking happened. | | `actions` | array | Canonical action log of every tool call the agent executed during the conversation. See [Actions log](#actions-log) below. Empty array when HIPAA is enabled. | ### Chat-only fields | Field | Type | Description | | - | - | - | | `messageCount` | number | Number of messages exchanged. | | `lastMessageAt` | string (ISO 8601) | Timestamp of the final message. | | `contactId` | string (UUID) \| null | Contact record, if one was matched/created. Chat payloads also include this convenience field at the root. | ### Web Agent fields Web Agent payloads use the voice-style structure — `endedReason`, `durationSeconds` on the root — plus `channel: "web"`. ## Example payloads ### Voice call ```json theme={null} { "event": "call.completed", "channel": "voice", "conversationId": "0a9b8c7d-...", "workspaceId": "w1w2w3-...", "direction": "outbound", "endedReason": "customer-ended-call", "durationSeconds": 245, "startedAt": "2026-04-23T14:30:00Z", "endedAt": "2026-04-23T14:34:05Z", "contact": { "contactId": "a1b2c3d4-...", "firstName": "John", "lastName": "Doe", "phone": "+14155551234", "email": "john@example.com", "contactBusinessName": "Acme Co", "tags": ["lead", "interested"], "customFields": { "budget": 5000, "is_returning": true, "tier": "gold" } }, "agent": { "agentId": "x1y2z3-...", "name": "Sales Agent", "phone": "+14155550001" }, "analysis": { "summary": "Customer confirmed interest and booked a demo for Friday at 2pm.", "outcome": "Appointment booked", "disposition": "Appointment booked", "dataExtraction": { "budget": 5000, "timeline": "Q3 2026", "demo_requested": true } }, "appointmentDate": "2026-04-26T18:00:00.000Z", "actions": [ { "type": "appointment_booked", "at": "2026-04-23T14:33:47Z", "metadata": { "scheduledFor": "2026-04-26T18:00:00.000Z", "calendarName": "Sales calendar" } } ], "transcript": "User: Hi, I'm calling about the demo.\nAssistant: Hello John, happy to help. Are you free Friday?\nUser: Friday at 2pm works.\nAssistant: Great, you're booked for Friday at 2pm." } ``` Each turn sits on its own line (`Speaker: text`), separated by `\n`. Split on newlines to reconstruct the turn-by-turn conversation. ### Actions log `actions` is a chronological array of every tool call the agent ran during the conversation. Each entry has: | Field | Type | Description | | - | - | - | | `type` | string | One of `appointment_booked`, `call_transfer`, `call_ended_by_assistant`, `sms_sent`, `email_sent`, `custom_action`, `mcp_action`. | | `at` | string (ISO 8601) | When the action fired. | | `metadata` | object | Type-specific payload — booking details, transfer destination, message body, etc. Internal IDs are stripped. | Failed tool calls are not emitted. The full unredacted action log (including tool args) is kept on the conversation record itself; `actions` here is sanitized for outbound delivery. When **HIPAA compliance** is enabled on the agent, `actions` is sent as an empty array since tool arguments can carry protected health information. ### Chat conversation (SMS / Instagram / email / web chat) ```json theme={null} { "event": "conversation.completed", "channel": "sms", "conversationId": "0a9b8c7d-...", "workspaceId": "w1w2w3-...", "contactId": "a1b2c3d4-...", "messageCount": 8, "startedAt": "2026-04-23T14:30:00Z", "endedAt": "2026-04-23T14:45:00Z", "lastMessageAt": "2026-04-23T14:45:00Z", "contact": { "contactId": "a1b2c3d4-..." }, "agent": { "agentId": "x1y2z3-...", "name": "Support Agent" }, "analysis": { "summary": "Visitor asked about pricing tiers and agreed to a follow-up email.", "outcome": "Follow up", "disposition": "Follow up", "dataExtraction": { "interest": "pricing", "seats_needed": 12 } }, "transcript": "[2026-04-23 14:30:12] User: How much does this cost?\n[2026-04-23 14:31:05] Assistant: Our plans start at $49/mo.\n[2026-04-23 14:43:20] User: Can you email me the details?\n[2026-04-23 14:43:58] Assistant: Sure, sending those over now." } ``` Text-channel transcripts prefix each line with `[YYYY-MM-DD HH:mm:ss] ` in your workspace timezone (no offset suffix). If a workspace hasn't set a timezone, times render in UTC. Strip the leading `[...] ` to recover the plain `Speaker: text` line. ### Web Agent conversation ```json theme={null} { "event": "conversation.completed", "channel": "web", "conversationId": "0a9b8c7d-...", "workspaceId": "w1w2w3-...", "endedReason": "customer-ended-call", "durationSeconds": 132, "startedAt": "2026-04-23T14:30:00Z", "endedAt": "2026-04-23T14:32:12Z", "contact": { "contactId": "a1b2c3d4-...", "firstName": "Jane", "lastName": "Smith", "phone": null, "email": "jane@example.com", "contactBusinessName": null, "tags": [], "customFields": {} }, "agent": { "agentId": "x1y2z3-...", "name": "Website Assistant" }, "analysis": { "summary": "Visitor asked about onboarding and asked for a demo link.", "outcome": "Follow up", "disposition": "Follow up", "dataExtraction": { "intent": "demo_request", "team_size": 25 } }, "appointmentDate": null, "actions": [ { "type": "email_sent", "at": "2026-04-23T14:32:05Z", "metadata": { "to": "jane@example.com", "subject": "Your demo link", "body": "Hi Jane, here's the link..." } } ], "transcript": "User: Hi, I was looking at your pricing page.\nAssistant: Happy to help. What size is your team?\nUser: About 25 people.\nAssistant: I'll send over a demo link tailored to that." } ``` ## Field-value typing `analysis.dataExtraction` and `contact.customFields` ship values as the JSON primitive that matches each field's declared type. The mapping: | Declared type | JSON output | Example raw value | Example output | | - | - | - | - | | `number` | `number` (finite) or `null` | `"42"` / `"3.14"` / `"-7"` | `42` / `3.14` / `-7` | | `number` (unparseable) | `null` | `"abc"` / `""` / `"NaN"` | `null` | | `boolean` | `true` / `false` / `null` | `"true"` / `"TRUE"` / `"False"` | `true` / `true` / `false` | | `boolean` (non-canonical) | `null` | `"yes"` / `"1"` / `""` | `null` | | `text` / `string` | `string` | `"gold"` | `"gold"` | | `date` | `string` (ISO 8601) | `"2026-05-20"` | `"2026-05-20"` | | unset / unknown | `string` (raw) | `"anything"` | `"anything"` | Notes: * A field that is not yet populated for the conversation/contact is omitted from the map rather than emitted as `null`. * `null` in `customFields` / `dataExtraction` always means *the stored value could not be coerced to the declared type* — log it on your side if you care about type drift. * Booleans accept canonical `'true'` / `'false'` only (case-insensitive, trimmed). Legacy non-canonical values like `'yes'` / `'1'` are intentionally surfaced as `null` instead of silently flipped to `false`. * Dates are emitted as the raw stored string (typically ISO 8601). JSON has no native date type. ## Security * **Always use HTTPS.** Nedzo blocks requests to non-public IPs and metadata endpoints but you should also refuse plaintext HTTP on your end. * **Verify the source.** Nedzo does not currently sign outbound webhooks. If you need to verify the sender, host your endpoint behind an auth gateway, allowlist Nedzo IP ranges, or include a shared secret in the URL path. * **Be idempotent.** Use `conversationId` as your idempotency key so duplicate deliveries (e.g. from network retries on your side) don't create duplicate records. ## Related * [Conversation ended workflow trigger](/workflows/triggers/conversation-completed) — to branch Nedzo workflows after any voice, chat, or web conversation, use this trigger instead of the webhook. * [Workflow webhook trigger](/workflows/triggers/webhook) — for sending requests *into* Nedzo from your own systems. # Prompt and identity Source: https://docs.nedzo.ai/agents/prompts-and-models Configure your AI agent's system prompt, tone of voice, answer length, voice, and language. Define conversation goals, rules, and the identity that makes it unique. The **Prompt** tab is where you set your agent's opening line and write its instructions. This is the core of what makes your agent unique. ## System prompt The system prompt is the main set of instructions for your agent. It defines the agent's personality, goals, rules, and conversation flow. Write your prompt in the large box under the **Opening line** card on the Prompt tab. A long prompt scrolls inside the box. A token counter in the bottom corner shows the approximate size. **Tips for good prompts:** * Be specific about the agent's role and goals * Define what the agent should and shouldn't do * Include example phrases or responses * Describe how to handle edge cases Use the **Enhance with AI** button to get suggestions for improving your prompt. ## Opening line The opening line is the first thing your agent says when a conversation starts. Set it in the **Opening line** card at the top of the Prompt tab. Leave it empty to let the agent open in its own words. **Example:** *"Hi there, is this `{{contact.firstName}}`?"* ## Variables Use variables in your prompt and opening line to personalize conversations. Click the **Variables** button to see all available options. Nedzo uses **dot notation** for variables — `{{contact.firstName}}`, `{{trigger.conversation.summary}}`, etc. Pick the namespace, then drill in. The legacy flat syntax (`{{contactFirstName}}`, `{{contactEmail}}`, etc.) still works for backwards compatibility with existing prompts and workflows. New prompts should use dot notation — it's clearer, scales to nested data like custom fields, and matches the variable picker. ### System variables | Variable | Description | Example | | - | - | - | | `{{date}}` | Current date | 2025-01-15 | | `{{time}}` | Current time | 2:30 pm | | `{{dayOfWeek}}` | Day of the week | Monday | ### Contact variables | Variable | Legacy alias | Description | | - | - | - | | `{{contact.firstName}}` | `{{contactFirstName}}` | Contact's first name | | `{{contact.lastName}}` | `{{contactLastName}}` | Contact's last name | | `{{contact.email}}` | `{{contactEmail}}` | Contact's email address | | `{{contact.phone}}` | `{{contactPhone}}` | Contact's phone number | | `{{contact.companyName}}` | `{{contactBusinessName}}` | Contact's company name | Custom fields are available under `{{contact.customFields.}}`. ### Custom fields You can create workspace-level custom fields for data that's specific to your business in **Settings > Workspace > Custom fields & Tags**. These are available across all agents in the workspace. ## Tone of voice and answer length Set how your agent sounds on the agent's **Identity** tab. You also pick both when you create an agent. **Tone of voice:** | Tone | How the agent sounds | | - | - | | Friendly | Warm and upbeat | | Casual | Relaxed and low-key, with everyday words and contractions. Calm, not bubbly: at most one exclamation mark per reply. | | Neutral | Plain and even. This is the default. | | Direct | Short, clipped replies with no small talk | | Professional | Polished and courteous, with no exclamation marks | | Humorous | Light and witty, with one small joke per reply | **Answer length:** Concise, Standard (the default), or Thorough. Answer length is the only length setting. Chat agents used to have a second one, **Response Length**, in the Settings tab. It is gone, so the two can no longer clash. Both settings apply on every conversation, on voice, chat, and web agents. This holds even when you leave them on Neutral and Standard. Nedzo picks the AI model for every agent. There is no model or temperature to set, and the model doesn't show in Conversation Logs or in API responses. ## Language Set the agent's language on the **Identity** tab, under **Voice and language**. Supported languages: * English * Spanish * Portuguese * French * German * Dutch * Chinese * Japanese Changing the language resets your voice selection, since voices are language-specific. ## Voice Give your agent a natural-sounding voice. Voice settings are available for voice agents and web agents. ### Voice selection Pick a voice on the **Identity** tab, under **Voice and language**. The section has three pickers: **Language**, **Multilingual** and **Voice**. The **Voice** list holds every voice for your language, across all its accents. There is no separate step to pick an accent first. Each option shows the voice name with two tags: * **Gender** — Male or Female * **Accent** — the variation, for example American or British For example, an English voice can read *Emma* with the tags *Female* and *American*. You can search the list by name or by a tag, like "British". If you have not picked a voice yet, the picker starts on the default voice for the language. Click the **play button** next to any voice to preview it before selecting. ### Voice speed Adjust how fast your agent speaks with the speed slider: | Speed | Effect | | - | - | | 0.7x | Slower, more deliberate | | 1.0x | Normal speed (default) | | 1.2x | Slightly faster | Slower speeds work well for complex information. Faster speeds feel more natural for casual conversations. # Agent types Source: https://docs.nedzo.ai/agents/voice-agents Learn about the three Nedzo agent types: voice agents for phone calls, chat agents for SMS and social messaging, and web agents for your website. Nedzo supports three agent types, each designed for a different channel. All agent types share the same prompt, actions, and knowledge base configuration — the differences are in how they communicate. ## Voice agents Voice agents handle phone calls using AI. Every voice agent does both: it answers calls that come in to its number, and it places calls out from that same number. There is no direction to choose. That means one agent can cover reception and intake on the same number it uses for lead qualification, appointment reminders, follow-ups and outreach campaigns. ### Phone number assignment Voice agents need a phone number to make or receive calls. Assign one from the **Settings** tab under **Telephony**. **Purchased numbers** — Phone numbers you own in your workspace. One number belongs to one agent, and that number both answers and dials for it, so two agents can't share the same number. Every new account starts with one free US number, added to the workspace automatically at signup. Buying additional numbers requires a paid plan — see [buying phone numbers on a trial](/billing/managing-your-subscription#buying-phone-numbers-on-a-trial). You can buy local numbers in the **United States** and **Canada**. These are the only two countries in the buy picker. Toll-free numbers and numbers in other countries aren't offered. See [phone number rental](/billing/plans#phone-number-rental) for the price. **Verified Caller IDs** — Numbers you've verified so calls you place can show your existing business number. A verified caller ID can only be used for calls the agent places; it cannot receive calls, so an agent assigned one has no number to answer on. A new number is not connected to any agent. This covers the free signup number and every number you buy. Until you assign it to an agent, incoming calls ring the users assigned to the number. If no users are assigned, the call is not answered. ### Who answers a number The **Assignment** column on the **Phone Numbers** page shows who answers calls on each number. It reads **AI agent** when a voice agent answers the number, **Human** when calls ring the assigned users, and a dash for a verified caller ID. On plans on Nedzo's current pricing, open a number to see the routing in the **Manage Number** panel: | Option | What it means | | - | - | | **AI agent** | The agent that claims this number answers. The description says which channels it covers: calls and texts, calls only, or texts with calls ringing the assigned users. | | **Human** | Incoming calls ring the users assigned to the number, listed below the options. | An agent claims a number when you assign the number to it from the agent's **Settings** tab. A number can be claimed by one voice agent for calls and a different chat agent for texts. Either claim is enough for **AI agent** to show. You do not pick the agent on the Phone Numbers page. **AI agent** shows which state the number is in, and the only change you can make there is to **Human**. Choosing **Human** releases the number from its voice and chat agents, and calls go to the assigned users. To hand the number to an AI again, assign it to an agent from the agent's settings. When no agent claims the number, **AI agent** is disabled. Hover it to see why: *No agent has claimed this number yet. Assign it to an agent on the Agents page, then it will answer here.* Verified caller IDs have no routing choice, since they cannot receive calls. If a call comes in for an agent that is switched off or was deleted, the caller does not hear endless ringing. Nedzo answers, plays a short message asking them to call back later, and ends the call. ### Choosing a voice Pick your agent's voice on the agent's **Identity** tab, under **Voice and language**. Press the play button next to the voice picker to hear a short sample before you commit. See [Voice selection](/agents/prompts-and-models#voice-selection) for how the picker works. * Press play to hear the selected voice * Press it again to stop * Switch voices and press play again to hear the new one You no longer need to save and call the agent to find out what it sounds like. ### Test calls Click **Call my phone** in the agent's test panel to have the agent call you. * **60-second cooldown.** After a test call goes out, the same number is locked for 60 seconds. The button counts down. * **Confirm before a redial.** Any later test call to the same number asks you to confirm first. * **Limits.** Each user can place 5 test calls a minute, and each workspace 20 an hour. Past that you see a message saying when to try again. * **No contact changes.** A test call doesn't create or edit a contact. Numbers marked DND can't be test called. ### Call settings | Setting | Range | Default | Description | | - | - | - | - | | Max call duration | 1–60 min | 30 min | The call ends automatically after this time | | Ring duration | 1–60 sec | 30 sec | How long to ring before marking as "No Answer" | | Background sound | On/Off | On | Plays subtle office ambience to make the call feel natural | #### How Max call duration works Max call duration is a hard cap on the whole phone call. When the time runs out, the call hangs up, even mid-sentence. * **It applies to every outbound call.** Calls from the [Make a call](/api-reference/calls) API, the [Voice call](/workflows/actions/voice-call) workflow action, campaigns, and follow-up retries all end at the agent's Max call duration. * **It covers the full call.** The clock starts when the call is placed and keeps running after a [transfer](/agents/actions) to a person. A 10-minute limit means the whole call, transfer included, ends at 10 minutes. There is no extra buffer. * **Billing stops with it.** Call minutes are billed only up to the cap, so a call that stays connected after the conversation ends can't run up extra minutes. * **Default and range.** The limit is set in whole minutes from 1 to 60. If an agent has no value saved, calls end at 30 minutes. If your agent transfers calls to your team, set Max call duration long enough to cover the conversation with the person you transfer to. ### Voicemail Enable voicemail detection from the **Settings** tab. When turned on: * The agent detects when a call goes to voicemail * It leaves your configured voicemail message * The call is logged with a "Voicemail" outcome You can customize the voicemail message. The default is: *"Hi, could you please call me back?"* ### Email notifications Nedzo emails your team when something on a voice agent call needs to be seen. These go out for every voice agent, on every plan. | Email | When it is sent | Who gets it | | - | - | - | | **Appointment booked** | The agent booked an appointment on a call | Owner and admins | | **Live transfer started** | The agent transferred a caller to a person, and the call connected | Admins | | **Live transfer failed** | The agent tried to transfer a caller, and the transfer did not connect | Owner and admins | | **Missed call** | An inbound call was turned away because the number has no agent, or its agent is turned off | Admins | Each call sends each email once. A caller who hangs up before a transfer connects does not trigger a transfer email. Test calls send no emails. The **Missed call** email is sent at most once per number every 24 hours, so a number that keeps getting calls does not flood your inbox. It links to the **Phone Numbers** page, where you can assign the number to an agent or turn the agent back on. The other emails link to the conversation. Each recipient can turn these emails off in **Settings > Profile > Notifications**. ### HIPAA compliance HIPAA compliance mode is a **\$299/month add-on**. It works on every plan. When it is on, call recordings, transcriptions, and logs are not stored. You turn it on in the **Settings** tab under **Security & Compliance**. You need a signed BAA before you can buy the add-on. [Contact support](mailto:support@nedzo.ai) to get one in place. The toggle stays disabled until the add-on is active on your workspace. See [Add-ons](/billing/plans#add-ons) for pricing and the other add-ons. ### Mark calls read when they end Turn on **Mark calls read when they end** in the **Settings** tab to mark an incoming call read in Unibox as soon as it ends. Every ended call counts, including dropped, failed and unanswered calls. It is off by default. When it is off, incoming calls stay in the **Unread** tab so your team can follow up. Calls the agent places are always read, whatever this setting says. That covers calls from workflows and campaigns. They show in Unibox, but not in **Unread**, until the contact calls back or replies. *** ## Chat agents Chat agents handle text-based conversations across multiple messaging channels. They respond automatically and show all conversations in Unibox. ### Channels Enable the channels you want from the **Settings** tab under **Channels**. | Channel | Status | Description | | - | - | - | | SMS | Available | Responds to incoming text messages | | Instagram | Available | Responds to Instagram DMs | | Messenger | Available | Responds to Facebook Page messages | | Email | Available | Responds to inbound emails on a per-agent address | | WhatsApp | Coming soon | — | **SMS** — Select a phone number from your workspace. Each chat agent needs its own SMS number. **Instagram** — Pick one of the Instagram accounts connected to your workspace. Each account goes to one chat agent, and a chat agent has at most one Instagram account. DMs to that account are answered by the agent it is assigned to. Connect accounts first from **Integrations**. See [Instagram](/integrations/instagram). **Messenger** — Pick a connected Facebook Page. The Messenger and Instagram pickers are separate, so the Page you pick for Messenger does not change the Instagram account. **Email** — Each chat agent gets its own inbound email address on your verified sending domain. Configure it from the **Settings** tab once Email is enabled as a channel. ### Email channel Once Email is enabled, set a per-agent local part (the part before the `@`). Inbound emails sent to that full address are routed to this agent's conversations. | Setting | Description | | - | - | | Inbound email address | The local part of the address. Combined with your verified sending domain to form the full address (e.g. `support@mail.yourdomain.com`). | **Local part rules:** * Lowercase letters, numbers, dots, hyphens, and underscores only * Max 64 characters * No leading, trailing, or consecutive dots * Must be unique within a workspace — two agents in the same workspace can't share the same local part **Requirements:** * A verified sending domain on the workspace — set up under **Settings > Integrations > Email**. Without one, the inbound address input is hidden because there's no domain to attach to. * The Email channel must be enabled on the agent. **How it works:** * Inbound emails to the agent's address create or continue a conversation in Unibox under that agent. * The agent generates AI replies using its prompt, knowledge base, and configured actions — same engine that powers SMS, Instagram, and Messenger. * Replies sent automatically by the agent or manually from Unibox come **from the agent's own inbound address**, not from a generic notifications address. * Each agent's email conversations are scoped to that agent — two agents on the same workspace can have parallel email threads with the same contact. ### Channel-aware replies Chat agents know which channel each incoming message came from and adjust their replies accordingly. You write one prompt — Nedzo layers channel-specific guidance on top automatically. | Channel | Default tone | Default length | | - | - | - | | SMS | Direct, conversational | 1–2 short sentences (designed to fit a single SMS segment) | | Email | Polished, structured | Multi-paragraph with greeting and sign-off where appropriate | | Web chat | Friendly, helpful | 1–3 sentences, links allowed | | Instagram | Casual, light | Short, can use emoji | | Messenger | Conversational | Short, can use emoji | | WhatsApp | Conversational | Short — optimized for mobile | The same agent can run across multiple channels and stay on-brand on each one — an SMS reply won't be a wall of text, and an email reply won't be a single sentence. Channel-specific instructions you put in the system prompt always take precedence over the defaults. Text replies never use em or en dashes as punctuation. If the AI writes one, Nedzo swaps it for a comma, period, or colon before the message goes out. Ranges such as *9am – 5pm*, hyphens, links, and email addresses stay as they are. ### Response timing Set a delay (0–60 seconds) before the agent responds. A short delay makes conversations feel more natural. Configure this in the **Settings** tab. ### Manual message behavior When you or anyone on your team replies in a conversation, the agent stops responding on it. This is automatic on every channel and there is nothing to configure. A human reply means a human owns the conversation. The agent stays quiet until the conversation closes. The contact's next message then starts a new conversation, and the agent answers it. See [When conversations close](#when-conversations-close). See [AI controls](/unibox/ai-controls) for how this looks in Unibox and for the other pause reasons. ### Message limits Set a maximum number of AI messages per conversation to prevent runaway conversations. When the limit is reached, you can optionally send a final message — for example: *"Thanks for chatting! A team member will follow up with you shortly."* Leave the limit blank for unlimited messages. ### Trigger keywords Use **Trigger Keywords** when you want the agent to answer only some conversations, like a "comment PRICE" or "text DEMO" campaign. Every other conversation is left for your team. Find it in the agent's **Settings** tab. Type a keyword and press Enter. You can add up to 50. * **Empty list.** The agent replies to every message. This is the default. * **With keywords.** The agent stays quiet in a new conversation until the contact sends a message with one of the keywords. From then on, it replies until the conversation closes. * **Matching.** Case does not matter. A keyword matches a whole word or phrase, not part of a word. `PRICE` matches "price please" but not "pricey". * **After a close.** The next conversation needs a keyword again. * **Messages before the trigger** still show in Unibox, and your team can reply to them. * **Team replies** never trigger the agent. * **Email.** Only the new text of a reply is checked, not the quoted email chain below it. * **Conversations the agent already replied in** count as triggered, so adding keywords does not silence a conversation that is already running. Trigger keywords work on SMS, WhatsApp, Messenger, Instagram and email. They are a chat agent setting only. Web agents reply to every visitor. [Protection keywords](#protection-keywords) do the opposite: a match pauses the agent. ### Post-event behavior * **Pause after escalation** — The agent always stops replying once it hands a conversation to a person. There is no switch for this. * **Pause AI after appointment booked** — Stops the agent after a calendar booking is confirmed ### When conversations close Chat conversations close on their own. There is no setting for this. The same rules apply to every chat and web agent: * **SMS, WhatsApp, Messenger, Instagram and web chat** close after 1 hour with no new message. * **Email** closes after 24 hours with no new message. * **Every conversation** closes 24 hours after it started, even if messages keep coming. An escalated one is the exception, see below. * **A booking** closes the conversation right after the agent confirms it. * **An escalated conversation** stays open for your team. It closes 24 hours after the escalation, not when it goes quiet. Any message restarts the quiet time: one from the contact, the agent or your team. This is true even when the agent does not reply. When a conversation closes, it is marked read and leaves the **Unread** tab in Unibox, and the usual close steps run: AI summary, the [Conversation ended](/workflows/triggers/conversation-completed) workflow trigger, the [post-conversation webhook](/agents/post-conversation-webhook) and cost rollup. A closed conversation does not reopen by itself. The contact's next message starts a new conversation. If your team replies to a closed conversation, the reply starts a new one too, and Unibox moves you to it. Web chat is the exception: you cannot reply to a closed web chat, because the visitor has left that chat. They start a new one when they write again. You can still put a conversation back in **Unread** by hand with **Mark as unread** in Unibox. A team reply pauses the AI in that conversation until it closes. ### Protection keywords Add keywords that pause the agent when a contact uses them. For example: "manager", "human", "complaint". You can add up to 50 keywords. This is not an escalation: no one is emailed, and the conversation closes on the normal rules. When a contact sends a message containing one of these keywords, the agent pauses and the conversation is flagged for human review. *** ## Web Agent Web Agents let you embed an AI assistant directly on your website. Visitors can chat, talk, or both. ### Widget modes * **Chat** — Text-based conversation. This is the default. * **Voice** — Visitors can speak to your agent through their browser * **Both** — Visitors choose between voice and chat Set the mode in the **Settings** tab. New widgets start in Chat mode, so voice is opt-in. Voice also needs **Allow voice calls from visitors** switched on, in the same tab. It is off by default. When it is off, the widget offers chat only, even in Voice or Both mode. Turn it on to let visitors call the agent from the widget. ### Appearance | Setting | Options | Default | | - | - | - | | Position | Bottom Right, Bottom Center, Bottom Left, Center Right, Center Left, Top Right, Top Left | Bottom Right | | Theme | Light, Dark | Light | | CTA text | Any text (max 25 characters) | "Talk to an agent" | | Avatar | Upload PNG, JPEG, SVG, or WebP (max 2 MB) | Gradient default | ### Branding * **Show branding** — Toggle the branding footer on/off * **Branding text** — Customize the text shown in the footer. Leave it empty to use the default: *Powered by Nedzo AI*, or *Powered by* your brand name on a white-label account. ### Visitor context The widget automatically captures context about website visitors to give your agent and team more information: * **Page URL** — The page the visitor was on when they started the conversation. This shows up in Unibox so your team can see what the visitor was looking at. * **Contact details**: When the conversation ends, Nedzo fills the visitor's name, email, phone, and company on the contact from what they said. See [Contact details](/agents/call-analysis#contact-details). ### Markdown rendering AI responses in the chat widget render basic markdown formatting: * **Bold** and *italic* text * [Hyperlinks](url) — links open in a new tab User-typed messages are displayed as plain text. Markdown rendering works in both the embedded widget and the chat simulator. ### Multilingual support Web Agents respond in the visitor's language automatically. If a visitor writes in Spanish, the agent replies in Spanish — regardless of the language set in the Prompt tab. The language setting on the Prompt tab controls the agent's default language and voice. The multilingual behavior applies to chat mode only. ### Embedding on your site The web widget is on the Agency plan and up. On a plan without web chat, the **Widget** card in the **Settings** tab shows an upgrade prompt instead of the install code. See [Plans & Pricing](/billing/plans). The **Widget** card offers two ways to install. Pick one with the toggle above the code, then click the copy button. The code already holds your agent's ID. | Install method | Use it when | Where the code goes | | - | - | - | | **Website HTML** | You can edit your site's HTML | Paste it just before the closing `` tag | | **Google Tag Manager** | Your site is managed through Google Tag Manager, for example by an agency | A new **Custom HTML** tag in Google Tag Manager | #### Website HTML Copy the code and paste it into your site's HTML, just before the closing `` tag. ```html theme={null} ``` #### Google Tag Manager 1. In Google Tag Manager, add a new tag and pick **Custom HTML** as the tag type. 2. Paste the code from the **Google Tag Manager** option. 3. Set the trigger to **All Pages**. 4. Save the tag, then publish the container. The code looks like this: ```html theme={null} ``` Google Tag Manager runs it on every page and adds the widget for you. It needs inline scripts to run. If your site has a strict Content Security Policy that blocks inline scripts, use **Website HTML** instead. Always copy the code from the **Widget** card rather than typing it. On some setups the card adds a `data-api-url` attribute, and both methods carry it. #### One widget per page A page shows one widget, even if the code is on it twice. For example, a site with the HTML code and a Google Tag Manager tag gets one widget, not two. The first one to load starts, and the other stays quiet. If the first one fails to start, the next one takes over. If a page has codes for two different agents, the first one to load wins. Remove the code you don't want so the right agent shows. The widget loads asynchronously and won't slow down your page. # Agent actions Source: https://docs.nedzo.ai/api-reference/agents/actions Create and manage the built-in actions an agent can take: calendar, transfer, SMS, email, WhatsApp and Slack. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). An action is something the agent can do during a conversation, like book a meeting or send a text. Each kind has the same five endpoints. Replace `{kind}` with `calendar`, `transfer`, `sms`, `email`, `whatsapp` or `slack`. | Method | Path | What it does | | - | - | - | | `GET` | `/v1/agents/{agentId}/actions/{kind}` | List the agent's actions of this kind | | `POST` | `/v1/agents/{agentId}/actions/{kind}` | Create one. Returns `201`. | | `GET` | `/v1/agents/{agentId}/actions/{kind}/{actionId}` | Get one | | `PATCH` | `/v1/agents/{agentId}/actions/{kind}/{actionId}` | Update one. Send only the fields to change. | | `DELETE` | `/v1/agents/{agentId}/actions/{kind}/{actionId}` | Delete one. Returns `204`. | Every action has `name` (required when you create one), `condition` (when the agent should use it) and `isActive`. The fields for each kind are below. The API checks the same rules as the dashboard's server. ## Calendar `cal_com`, `calendly`, `leadconnector` or `google`. The provider must be connected in the dashboard. A calendar or event type id from [List calendars](/api-reference/agents/calendars). An IANA time zone like `America/New_York`. Leave it out and the agent asks the contact. A calendar action also adds booking steps to the agent's prompt, the same as in the dashboard. ## Transfer Transfer actions work on voice agents only, the same as in the dashboard. A chat or widget agent gets a `400`. `warm` (the agent introduces the caller first) or `cold` (it hands the call straight over). The number to transfer to, in E.164 format like `+14155551234`. What the agent says before it transfers. On an update you may leave it out, but you can't set it to empty. ## SMS and WhatsApp The number to send from. It must be a number in the agent's workspace. `contact` sends to the person in the conversation, `specific` sends to `recipientValue`, and `ask` lets the agent ask. Leave it out for `contact`. The number to send to. Needed when `recipientType` is `specific`. The message. It can use `{{variables}}` from `parameters`. Required for SMS. WhatsApp needs it too, since `fixed` is the only message mode today. Values the agent fills in from the conversation. ## Email The sender name. The part of the sender address before the `@`, like `support`. The subject line. The plain text body. The HTML body. `contact`, `specific` or `ask`, as above. The address to send to. Needed when `recipientType` is `specific`. A sender address in the workspace. Leave it out to use the default. ## Slack The Slack channel to post in. Slack must be connected in the dashboard. The channel's name, for display. The message. ## Response Each action comes back with its `id`, `kind`, `agentId`, its fields, `createdAt` and `updatedAt`. ```json Response theme={null} { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "kind": "sms", "agentId": "123e4567-e89b-12d3-a456-426614174000", "name": "Send booking link", "condition": "The caller wants to book", "isActive": true, "phoneNumberId": "8f14e45f-ceea-467a-9575-0d3c8f9e1a9b", "recipientType": "contact", "recipientValue": null, "messageTemplate": "Book here: https://example.com/book", "messageMode": "fixed", "parameters": [], "createdAt": "2026-10-11T10:00:00Z", "updatedAt": "2026-10-11T10:00:00Z" } ``` ## Errors | Status | When | | - | - | | `400` | A field is missing or wrong, `phoneNumberId` is not a number in the agent's workspace, or a transfer is on a chat or widget agent. | | `404` | The agent or the action is not in your API key's scope, the action belongs to another agent, or `{kind}` is not one of the six. | # Give an agent a phone number Source: https://docs.nedzo.ai/api-reference/agents/assign-phone-number PUT /agents/{agentId}/phone-number Give a voice agent a number to answer and call from, or a caller ID to call from. Give a chat agent a number to text on. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Agent UUID A number the workspace owns. A voice agent answers and calls from it. A chat agent answers texts on it. A verified caller ID the workspace owns. Voice agents only. The agent calls from it and answers on no number. Ask support for this id. The API cannot list caller IDs yet. Send exactly one of the two. * A voice agent keeps its other numbers when you give it a new one. To take them away, [take the agent's phone numbers away](/api-reference/agents/unassign-phone-number) first. * A chat agent texts on one number. Giving it a new one takes the old one away. * A number or caller ID another agent holds returns `409`. Take it off that agent first. ```bash cURL theme={null} curl -X PUT "https://api.nedzo.ai/v1/agents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/phone-number" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phoneNumberId": "4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c" }' ``` ```json Response theme={null} { "agentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "phoneNumberId": "4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c", "verifiedCallerIdId": null, "releasedPhoneNumberIds": [] } ``` ## Errors | Status | When | | - | - | | `400` | Neither or both fields were sent, a web agent was named, or a caller ID was sent for a chat agent | | `404` | The agent, number or caller ID is not found, or not in this API key's scope. The number and the agent must be in the same workspace | | `409` | Another agent holds this number or caller ID | # List calendars Source: https://docs.nedzo.ai/api-reference/agents/calendars GET /calendars List the calendars or event types a connected calendar provider offers, to set up calendar actions. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Use an `id` from this list as `calendarId` on a [calendar action](/api-reference/agents/actions#calendar). Connect the provider in the dashboard first. ## Query Parameters `cal_com`, `calendly`, `leadconnector` or `google`. Needed for an account API key. ```json Response theme={null} { "calendars": [ { "id": "https://api.calendly.com/event_types/ABC", "name": "30 Minute Meeting", "owner": "personal" } ], "warnings": [] } ``` `warnings` says why the list may be incomplete, for example when the connected account cannot see every team calendar. It is empty when the list is complete. ## Errors | Status | When | | - | - | | `400` | `provider` is missing or not one of the four. | | `404` | The workspace is not in your API key's scope, or the provider is not connected. | | `502` | The provider did not answer. Try again. | # Create agent Source: https://docs.nedzo.ai/api-reference/agents/create POST /agents Create a new AI agent in a Nedzo workspace via the REST API. Configure the agent type, prompt, voice, language, actions, and knowledge base. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Create a new agent within a workspace. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Common Fields Agent name (1-255 characters) Type of agent to create. Valid values: `Voice`, `Chat`, `Widget` **Deprecated — do not send.** An agent has no direction: one phone number belongs to one agent, and that number both answers incoming calls and places outgoing ones. The value is still accepted so existing integrations keep working, but it is not stored, and the created agent comes back with `direction: null`. Creating an agent that places calls requires no special setting. Assign it a phone number and it can both answer and dial. One exception, and the reason to omit the field rather than pass it: explicitly sending `direction: "outbound"` still requires an approved KYC status on the account, and returns `403` if the account is not verified. Omitting the field avoids that check entirely. Workspace UUID (required for account API keys, optional for workspace API keys) System prompt for the agent Agent language. Valid values: `english`, `spanish`, `french`, `german`, `portuguese`, `dutch`, `chinese`, `japanese` (default: "english") Whether the agent is active (default: true) ## Type-Specific Fields Opening line the agent says when starting a conversation Voice ID for text-to-speech Enable voicemail detection (default: false). Applies to calls the agent places. Message to leave on voicemail Enable background sound (default: true) When this is enabled, no logs, recordings, or transcriptions will be stored (default: false) Maximum call duration in minutes, 1-60 (default: 30) Voice speed multiplier, 0.5-1.5 (default: 1.0) URL to receive a POST request after each conversation ends. The payload includes the transcript, summary, outcome, duration, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. **Deprecated.** Use `postConversationWebhookUrl` instead. Still accepted on input for backwards compatibility; responses always use the new field. URL to receive a POST request after each chat conversation ends (SMS, Instagram, Messenger, email, or web chat). The payload includes the transcript, summary, channel, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. URL to receive a POST request after each web agent conversation ends. The payload includes the transcript, summary, outcome, duration, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/agents" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Support Agent", "agentType": "Voice", "workspaceId": "123e4567-e89b-12d3-a456-426614174000", "prompt": "You are a helpful customer support assistant.", "openingLine": "Hello! How can I help you today?", "language": "english", "voiceId": "voice_123", "backgroundSound": true, "callDuration": 30, "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": null, "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Custom actions Source: https://docs.nedzo.ai/api-reference/agents/custom-actions Create custom actions that call your own endpoints, test them, and attach them to agents. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). A custom action calls your endpoint during a conversation. It belongs to the workspace, so one action can serve many agents. See [Actions](/agents/actions) for the request your endpoint gets. | Method | Path | What it does | | - | - | - | | `GET` | `/v1/custom-actions` | List the workspace's custom actions | | `POST` | `/v1/custom-actions` | Create one. Returns `201`. | | `GET` | `/v1/custom-actions/{id}` | Get one | | `PATCH` | `/v1/custom-actions/{id}` | Update one | | `DELETE` | `/v1/custom-actions/{id}` | Delete one. Every agent stops using it. Returns `204`. | | `POST` | `/v1/custom-actions/{id}/test` | Call the endpoint once and see what it answers | | `GET` | `/v1/agents/{agentId}/custom-actions` | List the custom actions an agent uses | | `POST` | `/v1/agents/{agentId}/custom-actions` | Attach one: send `{ "customActionId": "..." }`. Returns `201`. | | `DELETE` | `/v1/agents/{agentId}/custom-actions/{customActionId}` | Detach one. The action stays in the workspace. Returns `204`. | An account API key sends `workspaceId` in the body on create, and in the query on list. ## Fields The action's name. When the agent should use it. `GET`, `POST`, `PUT`, `PATCH` or `DELETE`. Your endpoint. It must be a public address. We refuse private and internal addresses. What the agent fills in from the conversation. Each has `name`, `description`, `type` (`string`, `number` or `boolean`) and `required`. Whether agents can use it. Headers to send with every call. On update this replaces all headers. A name may only use letters, digits and `!#$%&'*+-.^_`|\~`, a value can't hold a line break, and you can't set `Host`, `Content-Length`, `Transfer-Encoding`or`Connection`. `bearer`, `api\_key`or`basic`. Send `null`on update to remove the auth. The secret for`authType`. Needed when you set `authType\` for the first time. `authValue` and header values are write only. We never send them back. A response shows `hasAuth` and the header names. If you change `url` to another address (a different host, http or https, or port), send `authValue` and `headers` again in the same request, or set `authType` to `null` and `headers` to `{}` to remove them. The saved ones stay with the old address, and we refuse the change with a `400`. ```json Response theme={null} { "id": "5a3c1e2b-8d7f-4b6a-9c0e-1f2a3b4c5d6e", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "Check zip code", "condition": "The caller gives a zip code", "method": "POST", "url": "https://example.com/coverage", "parameters": [{ "name": "zip", "type": "string", "required": true }], "isActive": true, "authType": "bearer", "hasAuth": true, "headerNames": ["X-Team"], "createdAt": "2026-10-11T10:00:00Z", "updatedAt": "2026-10-11T10:00:00Z" } ``` ## Test a custom action `POST /v1/custom-actions/{id}/test` The values the agent would fill in, like `{ "zip": "94107" }`. We call your endpoint once with the saved URL, headers and auth, the same way a live call would. We wait up to 30 seconds and follow up to 5 redirects. A redirect to a private or internal address is refused. When a redirect goes to another address (a different host, http or https, or port), we do not send your auth there. Your other headers go with it. You can test 10 times a minute per workspace. ```json Response theme={null} { "success": true, "message": "Endpoint responded with 200 OK", "status": 200, "statusText": "OK", "body": "{\"covered\":true}", "durationMs": 182, "error": null } ``` `body` holds up to 4,000 characters of the answer. When the call fails, `success` is `false` and `error` says why. ## Errors | Status | When | | - | - | | `400` | A field is missing or wrong, the URL is not a public address, `authType` came without `authValue`, a header is not allowed, or `url` moved to another address without the auth and headers. | | `404` | The action or agent is not in your API key's scope, or the action is in another workspace than the agent. | | `409` | The action is already attached to the agent. | | `429` | More than 10 tests in a minute. Wait a minute. | # Delete agent Source: https://docs.nedzo.ai/api-reference/agents/delete DELETE /agents/{id} Permanently delete an AI agent by UUID via the Nedzo REST API. Removes the agent and its configuration from the workspace. Cannot be undone. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). The unique UUID of the Agent ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/agents/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} // 204 No Content ``` # Duplicate agent Source: https://docs.nedzo.ai/api-reference/agents/duplicate POST /agents/{agentId}/duplicate Copy an agent into a new agent in the same workspace. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Copies the agent's settings, actions, extraction fields, knowledge links and MCP servers into a new agent named "(Copy)". Phone numbers are not copied. The copy lists the same knowledge, but it cannot answer from it yet. Detach and attach each knowledge item on the copy. See [Agent knowledge](/api-reference/agents/knowledge). Copying a HIPAA agent needs the HIPAA add-on. ## Path Parameters The agent to copy. ```json 201 theme={null} { "id": "6f1d2e3c-4b5a-4968-8776-5a4b3c2d1e0f", "warnings": [] } ``` `warnings` lists the parts that were not copied. Get the new agent with [Get agent](/api-reference/agents/get). ## Errors | Status | When | | - | - | | `403` | The agent has HIPAA on and the account has no HIPAA add-on. | | `404` | The agent is not in your API key's scope. | # Extraction fields Source: https://docs.nedzo.ai/api-reference/agents/extraction-fields Choose the details an agent pulls out of each conversation. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). After each conversation, the agent fills in its extraction fields. They show in the conversation log and in the post-conversation webhook, and each one saves its value to a custom field on the contact. First name, last name, email, phone and company are filled in on their own, so they need no field. | Method | Path | What it does | | - | - | - | | `GET` | `/v1/agents/{agentId}/extraction-fields` | List the agent's fields | | `POST` | `/v1/agents/{agentId}/extraction-fields` | Create one. Returns `201`. | | `GET` | `/v1/agents/{agentId}/extraction-fields/{fieldId}` | Get one | | `PATCH` | `/v1/agents/{agentId}/extraction-fields/{fieldId}` | Update one | | `DELETE` | `/v1/agents/{agentId}/extraction-fields/{fieldId}` | Delete one. Returns `204`. | ## Fields The custom field in the workspace to save the value to. Get ids from the contact custom fields endpoint. What to pull out, in plain words. The agent reads this. The field's `name` and `type` come from the custom field, the same as in the dashboard. `targetContactField` is always `null` on new fields. ```json Response theme={null} { "id": "2c4e6f80-1a3b-4c5d-8e9f-0a1b2c3d4e5f", "agentId": "123e4567-e89b-12d3-a456-426614174000", "name": "budget", "type": "number", "description": "The budget the caller gives, in dollars", "targetContactField": null, "targetCustomVariableId": "0e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b", "createdAt": "2026-10-11T10:00:00Z", "updatedAt": "2026-10-11T10:00:00Z" } ``` ## Errors | Status | When | | - | - | | `400` | A field is wrong, no custom field was chosen, a contact field was chosen, or the custom field is not in the agent's workspace. | | `404` | The agent or field is not in your API key's scope. | | `409` | The agent already has a field with this name. | # Get agent Source: https://docs.nedzo.ai/api-reference/agents/get GET /agents/{id} Retrieve a single AI agent by UUID via the Nedzo REST API. Returns the agent type, prompt, voice settings, actions, and knowledge base. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Retrieve a specific agent by its UUID. The unique UUID of the Agent ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/agents/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": null, "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Agent knowledge Source: https://docs.nedzo.ai/api-reference/agents/knowledge List, attach and detach the knowledge items an agent answers from. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). An agent answers questions from the knowledge items attached to it. Add items to the workspace's library first, in the dashboard or with the knowledge endpoints. The item must be in the agent's workspace. ## List an agent's knowledge `GET /v1/agents/{agentId}/knowledge` Returns the attached items. ```json Response theme={null} [ { "knowledgeItemId": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "name": "Pricing FAQ", "type": "text", "attachedAt": "2026-10-11T10:00:00Z" } ] ``` ## Attach knowledge `POST /v1/agents/{agentId}/knowledge` A knowledge item in the agent's workspace. We read the item's content and load it into the agent before we answer, so this can take a while for a large website or file. If the content cannot be read, nothing is attached. Returns `201` with the attached item. If the request times out, list the agent's knowledge before you try again. The attach may have finished. A `409` means the item is already attached. ## Detach knowledge `DELETE /v1/agents/{agentId}/knowledge/{knowledgeItemId}` The agent stops answering from the item. The item stays in the library. Returns `204`. ## Errors | Status | When | | - | - | | `400` | An id is not a valid UUID. | | `404` | The agent or the item is not in your API key's scope, or the item is not attached. | | `409` | The item is already attached. | | `422` | The item's content could not be read, for example a website that blocks us. | # List agents Source: https://docs.nedzo.ai/api-reference/agents/list GET /agents Retrieve all AI agents via the Nedzo REST API. Workspace keys return agents in that workspace; account keys return agents across all workspaces. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). List agents based on API key scope. Workspace API keys return agents in that workspace only. Account API keys return all agents across all workspaces, grouped by workspace. ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/agents" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Workspace API Key Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": null, "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] ``` ```json Account API Key Response theme={null} [ { "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "workspaceName": "My Workspace", "agents": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": null, "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] } ] ``` # MCP servers Source: https://docs.nedzo.ai/api-reference/agents/mcp-servers Add MCP servers to a workspace, test them, and choose which ones a voice agent uses. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). An MCP server gives a voice agent more tools. A workspace can have up to 10, and a voice agent can use up to 5. | Method | Path | What it does | | - | - | - | | `GET` | `/v1/mcp-servers` | List the workspace's MCP servers | | `POST` | `/v1/mcp-servers` | Add one. Returns `201`. | | `GET` | `/v1/mcp-servers/{id}` | Get one | | `PATCH` | `/v1/mcp-servers/{id}` | Update one | | `DELETE` | `/v1/mcp-servers/{id}` | Delete one. Returns `204`. | | `POST` | `/v1/mcp-servers/{id}/test` | Connect with the saved settings and list the tools | | `GET` | `/v1/agents/{agentId}/mcp-servers` | List the servers an agent uses | | `PUT` | `/v1/agents/{agentId}/mcp-servers` | Set the servers an agent uses: `{ "mcpServerIds": [...] }`. An empty list removes all. Voice agents only. | An account API key sends `workspaceId` in the body on create, and in the query on list. ## Fields The server's name. Each name is used once per workspace. The server URL. It must be a public `https` address. `bearer` or `api_key`. Send `null` on update to remove the auth. The secret for `authType`. Extra headers to send. `authValue` and header values are write only. We never send them back. A response shows `hasAuth` and the header names. If you change `url` to another address (a different host, http or https, or port), send `authValue` and `headers` again in the same request, or set `authType` to `null` and `headers` to `{}` to remove them. The saved ones stay with the old address, and we refuse the change with a `400`. ```json Response theme={null} { "id": "9b2f6c1a-3e4d-4a5b-8c7d-0e1f2a3b4c5d", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "Inventory", "url": "https://mcp.example.com/mcp", "authType": "bearer", "hasAuth": true, "headerNames": [], "toolCount": 4, "lastConnectedAt": "2026-10-11T10:00:00Z", "createdAt": "2026-10-11T09:00:00Z", "updatedAt": "2026-10-11T10:00:00Z" } ``` ## Test an MCP server `POST /v1/mcp-servers/{id}/test` We connect with the saved URL, headers and auth and ask for the tool list. A redirect to a private or internal address is refused. When a redirect goes to another address (a different host, http or https, or port), we do not send your auth there. Your other headers go with it. You can test 10 times a minute per workspace. ```json Response theme={null} { "success": true, "tools": [{ "name": "check_stock", "description": "Look up stock for a product" }] } ``` When the connection fails, `success` is `false` and `error` says why. ## Errors | Status | When | | - | - | | `400` | A field is wrong, the URL is not a public `https` address, the workspace already has 10 servers, an id in `mcpServerIds` is not a server in the agent's workspace, the agent is not a voice agent, or `url` moved to another address without the auth and headers. | | `404` | The server or agent is not in your API key's scope. | | `429` | More than 10 tests in a minute. Wait a minute. | # Test agent webhook Source: https://docs.nedzo.ai/api-reference/agents/test-webhook POST /agents/{agentId}/test-webhook Send a sample post-conversation webhook to check your endpoint. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Sends one sample [post-conversation webhook](/agents/post-conversation-webhook) built from the agent's own extraction fields and custom fields. We wait up to 10 seconds. ## Path Parameters The agent. ## Body The URL to test. Leave it out to use the agent's webhook URL. The channel to label the sample with: `voice`, `web`, `sms`, `email`, `whatsapp`, `facebook` (Messenger), `instagram` or `web_chat`. ```json Response theme={null} { "success": false, "error": "Webhook endpoint returned HTTP 500" } ``` `success` is `true` when your endpoint answered with a `2xx`. A redirect to a private or internal address is refused. You can test 10 times a minute per workspace. ## Errors | Status | When | | - | - | | `400` | The agent has no webhook URL and you sent none, or the URL is not a public address. | | `404` | The agent is not in your API key's scope. | | `429` | More than 10 tests in a minute. Wait a minute. | # Take an agent's phone numbers away Source: https://docs.nedzo.ai/api-reference/agents/unassign-phone-number DELETE /agents/{agentId}/phone-number The agent stops answering and calling on every number and caller ID it holds. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Agent UUID The numbers stay in the workspace. Only the agent lets go of them. ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/agents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/phone-number" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "agentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "phoneNumberId": null, "verifiedCallerIdId": null, "releasedPhoneNumberIds": ["4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c"] } ``` ## Errors | Status | When | | - | - | | `400` | `A web agent does not use a phone number.` | | `404` | `Agent not found`: the agent does not exist, or it is not in this API key's scope | # Update agent Source: https://docs.nedzo.ai/api-reference/agents/update PATCH /agents/{id} Update an existing AI agent by ID via the Nedzo REST API. Modify the agent prompt, voice, language, actions, or knowledge base configuration. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Update an agent's details. All fields are optional - only provided fields will be updated. ## Path Parameters The unique UUID of the Agent ## Common Fields Agent name (1-255 characters) Whether the agent is active System prompt for the agent Agent language. Valid values: `english`, `spanish`, `french`, `german`, `portuguese`, `dutch`, `chinese`, `japanese` **Deprecated and ignored.** An agent has no direction — its number both answers and places calls. Still accepted on Voice agents so existing integrations keep working, but it does not change the agent. Sending it on a Chat or Widget agent is still rejected. ## Settings for every agent type How the agent sounds. One of `Friendly`, `Casual`, `Neutral`, `Direct`, `Professional`, `Humorous`. How long its answers are. One of `Concise`, `Standard`, `Thorough`. Tell the contact they are talking to an AI. The AI disclosure line for each language, like `{ "english": "You are talking to an AI assistant." }`. An empty line is dropped. `null` uses the default line. ## Type-Specific Fields A field from another agent type's tab gets a `400` that names the field. Opening line the agent says when starting a conversation Voice ID for text-to-speech Enable voicemail detection. Applies to calls the agent places. Message to leave on voicemail Enable background sound When this is enabled, no logs, recordings, or transcriptions will be stored Maximum call duration in minutes, 1-60 Voice speed multiplier, 0.5-1.5 URL to receive a POST request after each conversation ends. The payload includes the transcript, summary, outcome, duration, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. Ask the caller to agree to recording. The recording consent line for each language. `null` uses the default line. The key a caller presses to say no to recording: one digit, `*` or `#`. The disclosure line for each language when the agent speaks in a cloned voice. **Deprecated.** Use `postConversationWebhookUrl` instead. Still accepted on input for backwards compatibility; responses always use the new field. The channels the agent answers on: `sms`, `email`, `whatsapp`, `instagram`, `messenger`. Leaving out `email` also frees the agent's inbound email address. Seconds to wait before it answers, 0-60, so a contact can finish typing. The most AI messages in one conversation. `null` means no limit. Send a last message when the limit is reached. The last message to send. Stop answering after an appointment is booked. Up to 50 words that stop the AI at once, so a teammate can take over. Up to 50 words. The agent answers only after a contact message has one of them. An empty list answers everything. Stop answering on rude messages. URL to receive a POST request after each chat conversation ends (SMS, Instagram, Messenger, email, or web chat). The payload includes the transcript, summary, channel, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. The name shown in the widget. The line under the name. What the widget offers: `voice`, `chat` or `both`. `light` or `dark`. Where it sits on the page: `bottom-right`, `bottom-center`, `bottom-left`, `center-right`, `center-left`, `top-right` or `top-left`. The button text. An `https` image URL for the avatar. `null` removes it. Show the "Powered by" badge. The badge text. Empty or `null` uses the default text. Let website visitors call the agent from the widget. Up to 50 words. The agent answers only after a visitor message has one of them. Stop answering on rude messages. A change to a widget agent shows on websites right away. URL to receive a POST request after each web agent conversation ends. The payload includes the transcript, summary, outcome, duration, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/agents/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Agent Name", "prompt": "Updated system prompt...", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "spanish", "voicemail": false, "voicemailMessage": "Please leave a message after the tone.", "callDuration": 30, "voiceId": "voice_123", "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Updated Agent Name", "agentType": "Voice", "direction": null, "prompt": "Updated system prompt...", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "spanish", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T11:00:00Z" } ``` # Make a call Source: https://docs.nedzo.ai/api-reference/calls POST /call Initiate an outbound AI voice call via the Nedzo REST API. Specify the agent, phone number, and optional metadata to start an automated conversation. This endpoint is deprecated. Use [Start a call](/api-reference/conversations/send-call) (`POST /v1/conversations/call`). It takes the same body and answers the same way, except for DND. A contact with Do Not Disturb (DND) on gets `403` there. Here it gets `400`, or `403` when the number is on the opt-out list, and the error text is not the same. This endpoint keeps working. UUID of the agent to use for the call Type of call target. Valid values: `contact`, `phoneNumber` Custom variables to pass to the assistant. These override contact field values. UUID of the contact to call Phone number to call (E.164 format, e.g., +14155551234) First name Last name Email address Business or company name Custom field values by field name. Field names must match existing custom field definitions. The agent must have a phone number assigned in **Agent Builder > Settings** before you can make outbound calls. Calls fail with `400 Bad Request` if the agent has no number connected — Nedzo never falls back to another workspace number, since that would surface the wrong caller ID to the recipient. Every call ends at the agent's **Max call duration** (set on the agent's **Settings** tab, 1 to 60 minutes, 30 by default). The cap covers the whole call, including any time after a transfer to a person. See [How Max call duration works](/agents/voice-agents#how-max-call-duration-works). Outbound calls go live when your trial converts to a paid plan. Until then, `/v1/call` refuses the call with `402` and type `outbound-trial-gate`, and no call is placed. The one exception is a number verified in your workspace, such as your own phone, which you can call during the trial. ## Errors | Status | Detail | Cause | | - | - | - | | `400` | `Agent does not have a phone number assigned. Please assign a number in Agent Builder > Settings before making outbound calls.` | The agent has no phone number connected. | | `400` | `Phone number is not active. Please activate it first.` | The agent's assigned number is in `pending` or another non-active status. | | `400` | `Invalid call type` | `type` is something other than `contact` or `phoneNumber`. | | `400` | `Contact is on the Do Not Disturb list and cannot be contacted` | A contact with this number has DND on, or the contact found by this email has DND on. No call was placed. | | `400` | `The agent's number cannot place outbound calls. Assign a US or Canada number from Nedzo, or a verified caller ID.` | The agent's number can't make outbound calls. | | `402` | `Outbound goes live when your trial converts. Go live now to start calling, texting, and emailing contacts.` | The account is on a trial. The `type` ends in `outbound-trial-gate`. | | `402` | Varies | The account has no plan, its subscription is not active, or its wallet has no balance left. The `type` ends in `payment-required`. | | `403` | `Recipient has Do Not Disturb (DND) on` | The number is on the opt-out list. No call was placed. | | `403` | `Could not verify Do Not Disturb status` | Nedzo couldn't check the opt-out list, so it placed no call. Try again. | | `403` | `Contact processing is restricted (GDPR Art.18)` | The contact's data is restricted. No call was placed. | | `403` | `Your plan does not include calls.` | Your plan does not include calls. | | `500` | `Could not look up the contact for this number. Please try again.` | Nedzo couldn't read the contacts on this number, so it placed no call. Try again. | | `500` | `Could not look up the contact for this email. Please try again.` | Nedzo couldn't read the contact with this email, so it placed no call. Try again. | | `500` | `Could not check this number against the Do Not Disturb list. Please try again.` | Nedzo couldn't check DND, so it placed no call. Try again. | | `500` | `Could not check this contact against the Do Not Disturb list. Please try again.` | Nedzo couldn't check DND, so it placed no call. Try again. | The other errors are the same as on [the new endpoint](/api-reference/conversations/send-call#errors). ```bash Call Contact theme={null} curl -X POST https://api.nedzo.ai/v1/call \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "123e4567-e89b-12d3-a456-426614174000", "type": "contact", "contactId": "456e4567-e89b-12d3-a456-426614174000" }' ``` ```bash Call Phone Number theme={null} curl -X POST https://api.nedzo.ai/v1/call \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "123e4567-e89b-12d3-a456-426614174000", "type": "phoneNumber", "phoneNumber": "+14155551234", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "businessName": "Acme Inc", "customFields": { "Lead Source": "Website", "Company Size": "50-100" } }' ``` ```json Response theme={null} { "conversationId": "123e4567-e89b-12d3-a456-426614174000" } ``` ```json 402 Trial theme={null} { "type": "https://api.nedzo.ai/errors/outbound-trial-gate", "title": "Payment Required", "status": 402, "detail": "Outbound goes live when your trial converts. Go live now to start calling, texting, and emailing contacts." } ``` # List campaign contacts Source: https://docs.nedzo.ai/api-reference/campaigns/contacts GET /campaigns/{id}/contacts The contacts in a campaign, with their calls, last outcome, next call and stop state. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). The campaign's contacts come from its contact view. There is no endpoint to add one contact. Change the view, or the contact, instead. Campaign UUID | Filter | Contacts | | - | - | | `all` | Every contact. | | `callable` | Still to be called. | | `stopped` | Stopped for any reason. | | `skipped` | Could not be called: no phone, no timezone, DND, or held back by another contact rule. | | `awaiting_callback` | The last call ended with a callback request. Nothing calls them back for you. | Contacts per page, 1 to 200. Contacts to skip, up to 100,000. ## Fields | Field | What it is | | - | - | | `firstName`, `lastName`, `phone` | From the contact. Null when the contact was deleted after it was added. | | `attemptsMade` | Calls this campaign placed to the contact. | | `lastOutcome` | The outcome of the last call, like `callback_requested`. Null before the first call has one. | | `nextAttemptAt` | When the contact is next due. Null when no call is planned. | | `lastAttemptAt` | When the last call was placed. | | `stoppedAt`, `stopReason` | When and why this campaign stopped calling the contact. Both null while it still can. Reasons include `no_phone`, `no_timezone`, `dnd`, `no_longer_matches_view`, `removed_by_user` and `api_stop_request`. | ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/campaigns/123e4567-e89b-12d3-a456-426614174000/contacts?filter=awaiting_callback" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "contactId": "456e4567-e89b-12d3-a456-426614174000", "firstName": "Alice", "lastName": "Wong", "phone": "+15551230000", "attemptsMade": 2, "lastOutcome": "callback_requested", "nextAttemptAt": null, "lastAttemptAt": "2026-10-11T14:30:00Z", "stoppedAt": null, "stopReason": null } ], "total": 1, "limit": 50, "offset": 0, "hasMore": false } ``` # Create a campaign Source: https://docs.nedzo.ai/api-reference/campaigns/create POST /campaigns Create a campaign that calls the contacts in a contact view. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). A campaign calls everyone in a contact view, with one voice agent, inside the days and hours you set. It is a draft until you set it live. When it goes live, it adds the contacts in the view right away, and keeps the list in step with the view after that. Before each call, the dialer checks DND and opt-outs. It never calls a contact with DND on. The workspace. Required for an account key. A workspace key uses its own workspace. The campaign name, up to 200 characters. The voice agent that places the calls. It must be in the same workspace. The contact view that decides who gets called. It must be in the same workspace. Create views with the contact views endpoints. Talk minutes the campaign may use each day. When it reaches this, its status becomes `capped` until the next calling day. Calls after the first one. `0` means call each contact once. Days the campaign calls on. `1` is Monday and `7` is Sunday. When calling starts each day, like `09:00`. On the quarter hour, and not before 9am. When calling stops each day, like `17:00`. On the quarter hour, after the start, and not after 8pm. A timezone for every contact, like `America/New_York`. Leave it out or send `null` to use each contact's own timezone. A zone that names no place, like `UTC`, `GMT` or a fixed offset, gets a `400`. `draft` or `live`. A live campaign starts calling at once. A locked workspace cannot add a campaign. ## Errors | Status | When | | - | - | | `400` | A field is missing or not valid. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP, or the workspace is locked. | | `404` | The workspace does not exist, or your API key cannot reach it. | ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/campaigns" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "September promo", "voiceAgentId": "aaa11111-bbbb-2222-cccc-333344445555", "savedViewId": "bbb11111-bbbb-2222-cccc-333344445555", "dailyCapTalkMinutes": 120, "maxFollowUps": 2, "callingDays": [1, 2, 3, 4, 5], "callingHoursStart": "09:00", "callingHoursEnd": "17:00" }' ``` ```json 201 theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "September promo", "voiceAgentId": "aaa11111-bbbb-2222-cccc-333344445555", "savedViewId": "bbb11111-bbbb-2222-cccc-333344445555", "dailyCapTalkMinutes": 120, "maxFollowUps": 2, "callingDays": [1, 2, 3, 4, 5], "callingHoursStart": "09:00:00", "callingHoursEnd": "17:00:00", "callingTimezone": null, "status": "draft", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ``` # Delete a campaign Source: https://docs.nedzo.ai/api-reference/campaigns/delete DELETE /campaigns/{id} Delete a campaign and stop its calls. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). The campaign stops placing new calls at once. Campaign UUID ## Errors | Status | When | | - | - | | `400` | A field is missing or not valid. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP, or the workspace is locked. | | `404` | The campaign does not exist, or your API key cannot reach it. | ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/campaigns/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```text 204 theme={null} (no body) ``` # Get a campaign Source: https://docs.nedzo.ai/api-reference/campaigns/get GET /campaigns/{id} Get one campaign by its id. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Campaign UUID ## Errors | Status | When | | - | - | | `400` | A field is missing or not valid. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP. | | `404` | The campaign does not exist, or your API key cannot reach it. | ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/campaigns/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "September promo", "voiceAgentId": "aaa11111-bbbb-2222-cccc-333344445555", "savedViewId": "bbb11111-bbbb-2222-cccc-333344445555", "dailyCapTalkMinutes": 120, "maxFollowUps": 2, "callingDays": [1, 2, 3, 4, 5], "callingHoursStart": "09:00:00", "callingHoursEnd": "17:00:00", "callingTimezone": null, "status": "draft", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ``` # List campaigns Source: https://docs.nedzo.ai/api-reference/campaigns/list GET /campaigns List the campaigns in a workspace, newest first. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). The workspace. Required for an account key. A workspace key uses its own workspace. Only campaigns with this status: `draft`, `live`, `paused`, `capped`, `out_of_funds` or `completed`. Campaigns per page, 1 to 100. Campaigns to skip, up to 100,000. ## Status | Status | What it means | | - | - | | `draft` | Not calling yet. | | `live` | Calling inside its days and hours. | | `paused` | Stopped by a person. Only a person starts it again. | | `capped` | Used its talk minutes for the day. It starts again on the next calling day by itself. | | `out_of_funds` | The account has no minutes or balance left. It starts again by itself when the account can pay. | | `completed` | Finished. | You can set `draft`, `live`, `paused` and `completed`. The dialer sets `capped` and `out_of_funds`. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/campaigns?workspaceId=789e4567-e89b-12d3-a456-426614174000&status=live" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [{ "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "September promo", "voiceAgentId": "aaa11111-bbbb-2222-cccc-333344445555", "savedViewId": "bbb11111-bbbb-2222-cccc-333344445555", "dailyCapTalkMinutes": 120, "maxFollowUps": 2, "callingDays": [1, 2, 3, 4, 5], "callingHoursStart": "09:00:00", "callingHoursEnd": "17:00:00", "callingTimezone": null, "status": "draft", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" }], "total": 1, "limit": 50, "offset": 0, "hasMore": false } ``` # Get campaign results Source: https://docs.nedzo.ai/api-reference/campaigns/results GET /campaigns/{id}/results The numbers from the campaign overview: contacts left, calls, pickups, bookings and talk minutes today. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Campaign UUID | Field | What it is | | - | - | | `remaining` | Contacts the campaign will still call. | | `dials` | Calls placed, ever. A contact called three times counts three. | | `pickups` | Calls a person answered. | | `booked` | Calls that booked an appointment. | | `minutesToday` | Talk minutes used today. The day starts at midnight in the campaign's timezone, or the workspace's when the campaign has none. | | `dailyCapTalkMinutes` | The daily limit `minutesToday` counts toward. | ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/campaigns/123e4567-e89b-12d3-a456-426614174000/results" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "campaignId": "123e4567-e89b-12d3-a456-426614174000", "status": "live", "remaining": 412, "dials": 1180, "pickups": 246, "booked": 31, "minutesToday": 88, "dailyCapTalkMinutes": 120 } ``` # Stop calling a contact Source: https://docs.nedzo.ai/api-reference/campaigns/stop-contact POST /campaigns/{id}/contacts/{contactId}/stop Stop one campaign from calling one contact. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). This stops this campaign only. Other campaigns can still call the contact. To stop all contact with a person, on every channel, turn on DND for the contact instead. Calling it twice keeps the first stop time and reason. It also works for a contact the campaign has not added yet: the campaign will never add them. The old path, `POST /v1/outbound-campaigns/{campaignId}/contacts/{contactId}/stop`, is VIP only now. Pro and every other plan get a `403` there. VIP keys can keep using it for one more release, then it is removed. Move to this path. Campaign UUID Contact UUID Why the contact is stopped, up to 200 characters. Shown on the campaign page. Defaults to `api_stop_request`. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/campaigns/123e4567-e89b-12d3-a456-426614174000/contacts/456e4567-e89b-12d3-a456-426614174000/stop" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "asked us to stop calling about the promo" }' ``` ```json Response theme={null} { "campaignId": "123e4567-e89b-12d3-a456-426614174000", "contactId": "456e4567-e89b-12d3-a456-426614174000", "stoppedAt": "2026-10-11T12:00:00Z", "stopReason": "asked us to stop calling about the promo" } ``` # Update, start or pause a campaign Source: https://docs.nedzo.ai/api-reference/campaigns/update PATCH /campaigns/{id} Change a campaign's settings, or start or pause it. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Send only the fields you want to change. The fields are the same as [Create a campaign](/api-reference/campaigns/create), except `workspaceId`. A field we do not know gets a `400`, so a typo never saves the rest of the change without it. A locked workspace cannot change a campaign. Campaign UUID `live` starts calling, `paused` stops it, `completed` ends it, `draft` takes it back to a draft. Setting `live` adds the contacts in the view right away. The dialer sets `capped` and `out_of_funds`, so you cannot send those. When a live campaign starts again, or you change its days, hours or timezone, every contact still to be called is checked against the new schedule. If you switch from a fixed timezone back to each contact's own timezone, contacts with no timezone of their own stop, because the campaign can no longer tell when to call them. ## Errors | Status | When | | - | - | | `400` | A field is missing, not valid, or one we do not know. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP, or the workspace is locked. | | `404` | The campaign does not exist, or your API key cannot reach it. | | `500` | The change saved, but a follow-up step failed. The message says what to send again. | ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/campaigns/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "live" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "September promo", "voiceAgentId": "aaa11111-bbbb-2222-cccc-333344445555", "savedViewId": "bbb11111-bbbb-2222-cccc-333344445555", "dailyCapTalkMinutes": 120, "maxFollowUps": 2, "callingDays": [1, 2, 3, 4, 5], "callingHoursStart": "09:00:00", "callingHoursEnd": "17:00:00", "callingTimezone": null, "status": "live", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ``` # Import contacts in bulk Source: https://docs.nedzo.ai/api-reference/contacts/bulk Import up to 10,000 contacts in one request. `POST /v1/contacts/bulk` imports 1 to 10,000 contacts and answers when it's done, with counts and one result per contact. Every plan can use it. Contacts are matched to existing ones by phone, then email. By default, only empty fields on a match are filled. Send `"overwriteExisting": true` to overwrite filled ones. `tagIds` adds tags to every imported contact. Every id must be a tag in the workspace, or the request gets a `400` listing the unknown ids and nothing is imported. The response's `tagsAttached` counts the tag links added. `tagAttachError` is `true` when the contacts imported but adding the tags failed. The request body can be up to 16 MB, which is about 10,000 typical contacts. A larger body gets a `413`. * `dnd: true` turns Do Not Disturb on. * Bulk import never turns DND off: `dnd: false` is ignored. Use [update](/api-reference/contacts/update) for that. * Custom field values use the field's id: `"customFields": [{ "fieldId": "...", "value": "Website" }]`. ```bash theme={null} curl -X POST "https://api.nedzo.ai/v1/contacts/bulk" \ -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "contacts": [{ "firstName": "Ana", "phone": "+14155551234" }, { "email": "bo@example.com", "dnd": true }] }' ``` ```json Response theme={null} { "total": 2, "created": 2, "updated": 0, "skipped": 0, "errors": 0, "tagsAttached": 0, "tagAttachError": false, "results": [{ "index": 0, "status": "created", "contactId": "123e4567-e89b-12d3-a456-426614174000", "reason": null }, { "index": 1, "status": "created", "contactId": "223e4567-e89b-12d3-a456-426614174000", "reason": null }] } ``` # Create contact Source: https://docs.nedzo.ai/api-reference/contacts/create POST /contacts Create a new contact in a Nedzo workspace via the REST API. Provide a phone number, name, email, tags, and custom field values to add a contact record. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). Workspace UUID (required for account API keys, optional for workspace API keys) First name Last name Email address Phone number (E.164 format) IANA timezone identifier (e.g. `America/New_York`, `Europe/Amsterdam`). Used by features that respect the contact's local time, such as workflow scheduling and campaign send windows. * **Optional.** When omitted, the timezone is auto-derived from the phone number (US area code or country code). If the phone can't be resolved to a region, the workspace's default timezone is used as fallback. If neither resolves, the field is left null. * Caller-supplied values always win — if you pass a value, it's stored as-is. * Must be a valid IANA timezone identifier; bogus strings are rejected with `400 Bad Request`. Do Not Disturb. `true` stops every outbound call and message to this contact, on every channel. Tag names to attach to the contact. Tags that don't exist yet in your workspace are auto-created. Tag attachment is **additive** — this endpoint never removes a tag from a contact. * Names are matched **case-sensitively** (`"VIP"` and `"vip"` are different tags), trimmed of leading/trailing whitespace, and must be 1–255 characters. * Pass an empty array (`[]`) to no-op (no tags attached, none removed). * Tags in the response are returned in alphabetical order regardless of input order. Custom field values keyed by the custom field name **as displayed in the dashboard** (e.g. `"Lead Source"` — including spaces and original casing; not a slug). Unknown keys are rejected with a `400`. Define new custom fields in **Settings > Workspace > Custom fields & Tags** before referencing them via the API. * Accepts `string`, `number`, `boolean`, or `null`. All values are persisted as text and **returned as strings** regardless of input type (e.g. `250` → `"250"`). * Pass `null` to clear a stored value. This endpoint **strictly validates the request body**. Unknown top-level fields are rejected with a `400 Bad Request`. Custom field values must use the `customFields` object — they cannot be added as top-level fields. `phone` and `email` are **unique per workspace** among non-deleted contacts. If another active contact in the same workspace already has the same `phone` or `email`, the request fails with `409 Conflict` and an `errors` array indicating which field collided. Use [`POST /contacts/upsert`](/api-reference/contacts/upsert) instead if you want create-or-update semantics. ```json 409 Conflict theme={null} { "type": "https://api.nedzo.ai/errors/conflict", "title": "Conflict", "status": 409, "detail": "A contact with this phone already exists in this workspace.", "instance": "/v1/contacts", "errors": [ { "field": "phone", "message": "A contact with this phone already exists in this workspace." } ] } ``` ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/contacts" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "dnd": false, "customFields": { "Lead Source": "Website", "MRR": 250 } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "dnd": false, "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Contact custom fields Source: https://docs.nedzo.ai/api-reference/contacts/custom-fields List, create, update and delete the custom fields contacts can hold. Every plan can use these endpoints. A contact's values are in its `customFields`, keyed by field name. Set them through create, update, upsert or bulk import. | Method | Path | What it does | | - | - | - | | `GET` | `/v1/contacts/custom-fields?workspaceId=&limit=&offset=` | List the workspace's custom fields, one page at a time | | `POST` | `/v1/contacts/custom-fields` | Create a field: `{ "workspaceId", "name", "type" }` | | `PATCH` | `/v1/contacts/custom-fields/{fieldId}` | Change a field's `name`, `type` or `position` | | `DELETE` | `/v1/contacts/custom-fields/{fieldId}` | Delete a field | The list is paged: `limit` (1 to 1,000, default 100) and `offset`, oldest first. It returns `{ items, total, limit, offset, hasMore }`; keep adding `limit` to `offset` while `hasMore` is true. `type` is one of `text` (the default), `number`, `date` or `boolean`. ```json A custom field theme={null} { "id": "5c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "Lead Source", "type": "text", "position": 0, "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ``` # Delete contact Source: https://docs.nedzo.ai/api-reference/contacts/delete DELETE /contacts/{id} Permanently delete a contact by UUID via the Nedzo REST API. Removes the contact record and associated data from your workspace. Cannot be undone. Contact UUID ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} // 204 No Content ``` # Get contact Source: https://docs.nedzo.ai/api-reference/contacts/get GET /contacts/{id} Retrieve a single contact by UUID via the Nedzo REST API. Returns the contact name, phone number, email, tags, metadata, and workspace details. Contact UUID ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "dnd": false, "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # List contacts Source: https://docs.nedzo.ai/api-reference/contacts/list GET /contacts Page through the contacts in a workspace via the Nedzo REST API, up to 1,000 per page. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). **Changed in October 2026:** this endpoint returns one page as an object, `{ items, total, limit, offset, hasMore }`. It used to return a bare array of at most 1,000 contacts. Read the contacts from `items`. Workspace UUID (required for account API keys, optional for workspace API keys) Only contacts with this tag, by name Match first name, last name, email or phone (contains, not case-sensitive) Created at or after this time (ISO 8601, for example `2026-10-01T00:00:00Z`). Also `createdBefore`, `updatedAfter` and `updatedBefore`. Contacts per page, from 1 to 1000 How many contacts to skip, up to 10,000,000. Contacts come oldest first, so add `limit` to `offset` for the next page while `hasMore` is true. ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/contacts?workspaceId=789e4567-e89b-12d3-a456-426614174000&limit=100&offset=0" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "dnd": false, "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ], "total": 2450, "limit": 100, "offset": 0, "hasMore": true } ``` # Contact tags Source: https://docs.nedzo.ai/api-reference/contacts/tags List, create, rename and delete tags, and add or remove a tag on a contact. Every plan can use these endpoints. `workspaceId` is required for account API keys and inferred for workspace API keys. | Method | Path | What it does | | - | - | - | | `GET` | `/v1/contacts/tags?workspaceId=&limit=&offset=` | List the workspace's tags, one page at a time | | `POST` | `/v1/contacts/tags` | Create a tag: `{ "workspaceId", "name" }` | | `PATCH` | `/v1/contacts/tags/{tagId}` | Rename a tag: `{ "name" }` | | `DELETE` | `/v1/contacts/tags/{tagId}` | Delete a tag and remove it from every contact | | `POST` | `/v1/contacts/{contactId}/tags` | Add a tag to a contact: `{ "tagId" }` or `{ "name" }` | | `DELETE` | `/v1/contacts/{contactId}/tags/{tagId}` | Remove a tag from a contact | The list is paged: `limit` (1 to 1,000, default 100) and `offset`, oldest first. It returns `{ items, total, limit, offset, hasMore }`; keep adding `limit` to `offset` while `hasMore` is true. Adding a tag by `name` creates it in the contact's workspace if it doesn't exist yet. Adding a tag the contact already has does nothing and returns `"added": false`. A tag added to a contact starts any **Contact tag added** workflow. Removing one starts any **Contact tag removed** workflow, never a tag added one. ```bash Add a tag theme={null} curl -X POST "https://api.nedzo.ai/v1/contacts/123e4567-e89b-12d3-a456-426614174000/tags" \ -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "vip" }' ``` ```json Response theme={null} { "contactId": "123e4567-e89b-12d3-a456-426614174000", "tag": { "id": "9b2f6c1e-1d2a-4f3b-8c4d-5e6f7a8b9c0d", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "vip", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" }, "added": true } ``` # Update contact Source: https://docs.nedzo.ai/api-reference/contacts/update PATCH /contacts/{id} Update an existing contact by ID via the Nedzo REST API. Modify the contact name, phone number, email, tags, or custom field values with a PATCH request. Contact UUID First name Last name Email address Phone number IANA timezone identifier (e.g. `America/New_York`). Pass `null` to clear the field. Used by features that respect the contact's local time. Must be a valid IANA timezone identifier; bogus strings are rejected with `400 Bad Request`. Do Not Disturb. `true` turns it on. `false` turns it off only when the API or a person in the dashboard turned it on. If the contact asked for it (for example by replying STOP), or Lead Connector, a workflow or an import set it, it stays on and you get a `409` saying who set it. It also stays on with a `409` if the contact's phone or email is on the Do Not Contact list (for example after a STOP reply). That includes a phone or email the same request sets. If the contact changes while DND is being turned off, you also get a `409`. Send the request again. Tag names to attach to the contact. Tags that don't exist yet in your workspace are auto-created. Tag attachment is **additive** — this endpoint never removes a tag. Names are matched case-sensitively, trimmed, and must be 1–255 characters. Omit the field (or pass `[]`) to leave tags unchanged. Adding a tag here fires the [Contact tagged](/workflows/triggers/contact-tagged) trigger, so any workflow listening for that tag runs. Only tags the contact doesn't already have fire it — re-sending a tag the contact already carries does nothing, so a CRM that re-pushes the same contact on every sync won't re-trigger your workflows. Custom field values keyed by the custom field name **as displayed in the dashboard** (e.g. `"Lead Source"` — including spaces and original casing; not a slug). Accepts `string`, `number`, `boolean`, or `null` (null clears a stored value). Values you provide overwrite the existing value for that key; other custom fields on the contact are unaffected. Unknown keys are rejected with a `400`. Values are persisted as text and returned as strings. At least one field must be provided. Unknown top-level fields are rejected with `400 Bad Request`. Custom field values must use the `customFields` object — they cannot be added as top-level fields. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Jane", "tags": ["gold"], "dnd": false, "customFields": { "MRR": 500 } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "Jane", "lastName": "Doe", "email": "jane@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["gold", "vip"], "dnd": false, "customFields": { "Lead Source": "Website", "MRR": "500" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T11:00:00Z" } ``` # Upsert contact Source: https://docs.nedzo.ai/api-reference/contacts/upsert POST /contacts/upsert Create or update a contact via the Nedzo REST API. Matches existing active contacts by phone first, falling back to email. Create or update a contact based on phone (primary) or email (fallback) matching against active (non-deleted) contacts. Returns `201` for new contacts and `200` for updates. At least one of `phone` or `email` must be provided. Matching only considers active contacts. If the only match is a soft-deleted contact, it is ignored and a brand new contact is created — the deleted record stays deleted with its data intact. By default, on a match, **provided** values in the request overwrite the existing contact's scalar fields and custom field values; fields you omit from the request are left untouched. Set `preserveExisting: true` to merge instead — only empty fields and missing custom field values are filled. **Tags are always additive** regardless of `preserveExisting`. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). Workspace UUID (required for account API keys, optional for workspace API keys) First name Last name Email address (used for matching if phone is not provided) Phone number in E.164 format (primary matching field) IANA timezone identifier (e.g. `America/New_York`). Used by features that respect the contact's local time. * **Only applied when creating a new contact.** When matching an existing contact, the existing timezone is preserved (or merged per `preserveExisting`); auto-derivation does not run on updates. * On create, when omitted, the timezone is auto-derived from the phone number with the workspace default as fallback. Caller-supplied values always win. * Must be a valid IANA timezone identifier; bogus strings are rejected with `400 Bad Request`. When `true`, populated fields on the matched contact are preserved — only empty fields and missing custom field values are filled in. When `false` (default), the request values overwrite the existing fields and custom field values. Tag attachment is always additive regardless of this flag. Do Not Disturb. `true` turns it on. `false` turns it off only when the API or a person in the dashboard turned it on. If the contact asked for it (for example by replying STOP), or Lead Connector, a workflow or an import set it, it stays on and you get a `409` saying who set it. It also stays on with a `409` if the contact's phone or email is on the Do Not Contact list (for example after a STOP reply). That includes a phone or email the same request sets. If the contact changes while DND is being turned off, you also get a `409`. Send the request again. Tag names to attach to the contact. Tags that don't exist yet are auto-created in your workspace. Tags are always **additive** — this endpoint never removes a tag from a contact, regardless of `preserveExisting`. Names are matched case-sensitively, trimmed, and must be 1–255 characters. Tags in the response are returned in alphabetical order. Adding a tag fires a workflow trigger. On a **new** contact it fires [Contact created](/workflows/triggers/contact-created) with the tags already attached. On a **matched existing** contact it fires [Contact tagged](/workflows/triggers/contact-tagged), once per tag the contact didn't already have. Re-sending tags the contact already carries fires nothing, so repeated syncs of the same contact won't re-trigger your workflows. Custom field values keyed by the custom field name **as displayed in the dashboard** (e.g. `"Lead Source"` — including spaces and original casing; not a slug). Accepts `string`, `number`, `boolean`, or `null` (null clears a stored value). Behavior on a match follows `preserveExisting`: when `false` (default), provided values overwrite; when `true`, only fill custom fields the contact doesn't have a value for. Unknown keys are rejected with a `400`. Values are persisted as text and returned as strings. This endpoint **strictly validates the request body**. Unknown top-level fields are rejected with `400 Bad Request`. Custom field values must use the `customFields` object. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/contacts/upsert" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "phone": "+14155551234", "email": "john@example.com", "firstName": "John", "lastName": "Doe", "tags": ["vip"], "dnd": false, "customFields": { "Lead Source": "Website", "MRR": 250 } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["vip"], "dnd": false, "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Contact views Source: https://docs.nedzo.ai/api-reference/contacts/views Saved contact filters. A view is the audience of a campaign. Every plan can use these endpoints. A view is the same saved filter the Contacts page uses, so it shows up in the dashboard too. | Method | Path | What it does | | - | - | - | | `GET` | `/v1/contacts/views?workspaceId=` | List the workspace's views | | `POST` | `/v1/contacts/views` | Create a view: `{ "workspaceId", "name", "filters", "sort" }` | | `GET` | `/v1/contacts/views/{viewId}` | Get a view | | `PATCH` | `/v1/contacts/views/{viewId}` | Change a view's `name`, `filters` or `sort` | | `DELETE` | `/v1/contacts/views/{viewId}` | Delete a view | Each filter is `{ "id", "fieldKey", "operator", "value" }`, the same shape the Contacts page saves. Every filter is checked when you save the view: an unknown `fieldKey`, an operator the field doesn't support, or a missing or badly formatted value gets a `400` that lists each problem. That matters because a view is a campaign's audience, and a filter that doesn't apply would match every contact. | fieldKey | Type | Operators | | - | - | - | | `name`, `email`, `phone`, `company` | text | `contains`, `not_contains`, `equals`, `not_equals`, `is_empty`, `is_not_empty` | | `tags` | tags (comma-separated names) | `all_of`, `none_of`, `is_empty`, `is_not_empty` | | `dnd` | yes/no | `equals`, `not_equals` | | `createdAt`, `lastActivity` | date | `equals`, `not_equals`, `is_greater`, `is_less`, `is_between`, `in_past_days`, `is_empty`, `is_not_empty` | | `custom:{{FieldName}}` | the custom field's type | the operators for that type; number fields: `equals`, `not_equals`, `is_greater`, `is_less`, `is_empty`, `is_not_empty` | Values by type: * **Yes/no** (`dnd` and yes/no custom fields): `Yes` or `No`. `true` and `false` also work, in any case. The view stores `Yes` or `No`, the same as the dashboard, so reading a view back shows `Yes` or `No`. `{ "fieldKey": "dnd", "operator": "equals", "value": "Yes" }` matches contacts on Do Not Disturb. * **Date**: a day as `YYYY-MM-DD`, for example `2026-01-31`. `is_between` takes two days joined by `..`, start first: `2026-01-01..2026-01-31`. Both days count. `in_past_days` takes a whole number of days, for example `30`. * **Number**: a number, for example `10` or `-2.5`. * `is_empty` and `is_not_empty` need no value. For a custom field, `FieldName` is its name with the spaces removed, for example `custom:{{LeadSource}}` for **Lead Source**. `value` is at most 1,000 characters and `id` at most 64. A view made through the API shows the account owner as its creator. If the account has no owner, creating a view returns a `409`. ```json Create a view theme={null} { "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "name": "VIP leads", "filters": [{ "id": "flt_1", "fieldKey": "tags", "operator": "all_of", "value": "vip" }], "sort": { "key": "created_at", "direction": "desc" } } ``` # Get a conversation Source: https://docs.nedzo.ai/api-reference/conversations/get GET /conversations/{id} Read one conversation log: summary, outcome, extracted fields, and the transcript, messages or call recording. Read only, on every plan. Use the `conversationId` that [Make a call](/api-reference/calls) returns to read a call's result once it ends. * **Calls** include `transcript`, `transcriptTurns` and `recordingUrl`. The recording link works for 15 minutes (`recordingUrlExpiresAt`); request the conversation again for a fresh one. A call with no recording, or one whose recording isn't stored with Nedzo, has `recordingUrl: null`. * **Text threads** include one page of `messages`, oldest first. `role` is `contact`, `ai` or `teammate`. * **Website widget sessions** include `transcript` and `transcriptTurns`. Conversation UUID Workspace UUID (required for account API keys, optional for workspace API keys) Text threads only: messages per page, from 1 to 500 Text threads only: how many messages to skip ## Transcript turns Calls and widget sessions have `transcriptTurns`, in the order they happened. | Field | What it is | | - | - | | `role` | `ai` for the agent, `customer` for the person, or `event` for something that happened on the call. Today an `event` marks a warm transfer to a person. Its `text` says what happened, and it is not speech. | | `text` | What was said, or what happened | | `offsetSeconds` | Seconds from the start of the call. `null` when it is not known. | ## Messages Text threads have `messages`, one page at a time, oldest first. Each page has `items`, `total`, `limit`, `offset` and `hasMore`. Calls and widget sessions have `messages: null`. | Field | What it is | | - | - | | `id` | Message UUID | | `direction` | `inbound` (from the contact) or `outbound` (to the contact) | | `role` | `contact`, `ai` (sent by the agent) or `teammate` (sent by a person on your team) | | `content` | The message text | | `subject` | The email subject. `null` for other channels. | | `status` | The delivery status the channel gave, or `null` | | `createdAt` | When the message was saved | `channel` and `outcome` take the same values as in [List conversations](/api-reference/conversations/list#channels). ## Errors | Status | When | | - | - | | `400` | A query value is not valid, or an account key leaves out `workspaceId` | | `402` | The account has no plan, or its subscription is not active | | `404` | The workspace or the conversation does not exist, or this key can't read it | ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/conversations/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "type": "voice", "channel": "call", "direction": "outbound", "status": "closed", "agentId": "456e4567-e89b-12d3-a456-426614174000", "contactId": "321e4567-e89b-12d3-a456-426614174000", "outcome": "appointment_booked", "summary": "The caller booked a cleaning for Tuesday at 10am.", "startedAt": "2026-10-01T15:02:11Z", "endedAt": "2026-10-01T15:05:40Z", "durationSeconds": 209, "createdAt": "2026-10-01T15:02:05Z", "updatedAt": "2026-10-01T15:06:02Z", "extractedFields": [ { "name": "Preferred day", "value": "Tuesday" } ], "transcript": "AI: Hi, this is Sam from Bright Dental...", "transcriptTurns": [ { "role": "ai", "text": "Hi, this is Sam from Bright Dental.", "offsetSeconds": 0.8 }, { "role": "customer", "text": "Hi Sam.", "offsetSeconds": 3.1 } ], "messages": null, "recordingUrl": "https://recordings.example/abc.mp3?signature=...", "recordingUrlExpiresAt": "2026-10-11T10:15:00Z" } ``` # List conversations Source: https://docs.nedzo.ai/api-reference/conversations/list GET /conversations Page through the conversation logs in a workspace: calls, text threads and website widget sessions, newest first. Read only, on every plan. Deleted conversations are never returned. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). Workspace UUID (required for account API keys, optional for workspace API keys) `voice` (phone calls), `chat` (text channels) or `web` (website widget) One of `call`, `sms`, `email`, `whatsapp`, `instagram`, `messenger`, `web_chat`, `web`. A channel that doesn't match `type` returns a `400`. A channel with no conversations returns an empty page. Calls only: `inbound` or `outbound`. Combined with a chat or web `type` or `channel`, it returns a `400`. Only conversations handled by this agent Only conversations with this contact `open`, `closed` or `snoozed` ISO 8601 date-time. Conversations created at or after it. ISO 8601 date-time. Conversations created before it. Conversations per page, from 1 to 100 How many conversations to skip, from 0 to 100,000. Add `limit` to `offset` for the next page while `hasMore` is true. To reach older conversations, narrow `startDate` and `endDate`. ## Channels | Channel | Type | What it is | | - | - | - | | `call` | `voice` | A phone call | | `sms` | `chat` | A text message thread | | `email` | `chat` | An email thread | | `whatsapp` | `chat` | A WhatsApp thread | | `instagram` | `chat` | An Instagram direct message thread | | `messenger` | `chat` | A Facebook Messenger thread | | `web_chat` | `chat` | A text thread that is not tied to any of the channels above | | `web` | `web` | A session in the website widget | ## Outcome `outcome` is one of `appointment_booked`, `booking_failed`, `transferred`, `follow_up`, `callback_requested`, `not_interested`, `disqualified`, `wrong_number`, `do_not_disturb`, `no_answer`, `voicemail`, `failed` or `other`. It is `null` when the conversation has no outcome yet. New values may be added, so treat a value you don't know as `other`. ## Errors | Status | When | | - | - | | `400` | A query value is not valid, `channel` does not match `type`, `direction` is set for a chat or web conversation, or an account key leaves out `workspaceId` | | `402` | The account has no plan, or its subscription is not active | | `404` | The workspace does not exist, or this key can't read it | ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/conversations?workspaceId=789e4567-e89b-12d3-a456-426614174000&type=voice&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "type": "voice", "channel": "call", "direction": "outbound", "status": "closed", "agentId": "456e4567-e89b-12d3-a456-426614174000", "contactId": "321e4567-e89b-12d3-a456-426614174000", "outcome": "appointment_booked", "summary": "The caller booked a cleaning for Tuesday at 10am.", "startedAt": "2026-10-01T15:02:11Z", "endedAt": "2026-10-01T15:05:40Z", "durationSeconds": 209, "createdAt": "2026-10-01T15:02:05Z", "updatedAt": "2026-10-01T15:06:02Z" } ], "total": 342, "limit": 20, "offset": 0, "hasMore": true } ``` # Start a call Source: https://docs.nedzo.ai/api-reference/conversations/send-call POST /conversations/call Place an outbound AI voice call with an agent, to a contact or a phone number. Place an outbound call with an agent. The response gives the `conversationId` at once, and the call is placed in the background. Read the result with [Get a conversation](/api-reference/conversations/get) once the call ends. Every plan with calls can use this endpoint. It takes the same body as [Make a call](/api-reference/calls) (`POST /v1/call`), which it replaces. Every call checks Do Not Disturb (DND) first. If the contact has DND on, or the number is on the opt-out list, the call is refused with `403` and no call is placed. UUID of the agent to use for the call Type of call target. Valid values: `contact`, `phoneNumber` Custom variables to pass to the assistant. These override contact field values. UUID of the contact to call Phone number to call (E.164 format, e.g., +14155551234) First name Last name Email address Business or company name Custom field values by field name. Field names must match existing custom field definitions. The agent must have a phone number assigned in **Agent Builder > Settings** before you can make outbound calls. Calls fail with `400 Bad Request` if the agent has no number connected. Nedzo never uses another number in the workspace, because the person would see the wrong caller ID. Every call ends at the agent's **Max call duration** (set on the agent's **Settings** tab, 1 to 60 minutes, 30 by default). The cap covers the whole call, including any time after a transfer to a person. See [How Max call duration works](/agents/voice-agents#how-max-call-duration-works). Outbound calls go live when your trial converts to a paid plan. Until then, this endpoint refuses the call with `402` and type `outbound-trial-gate`, and no call is placed. The one exception is a number verified in your workspace, such as your own phone, which you can call during the trial. ## Errors | Status | Detail | Cause | | - | - | - | | `400` | `Agent does not have a phone number assigned. Please assign a number in Agent Builder > Settings before making outbound calls.` | The agent has no phone number connected. | | `400` | `Phone number is not active. Please activate it first.` | The agent's assigned number is in `pending` or another non-active status. | | `400` | `Invalid call type` | `type` is something other than `contact` or `phoneNumber`. | | `400` | `The agent's number cannot place outbound calls. Assign a US or Canada number from Nedzo, or a verified caller ID.` | The agent's number can't make outbound calls. | | `402` | `Outbound goes live when your trial converts. Go live now to start calling, texting, and emailing contacts.` | The account is on a trial. The `type` ends in `outbound-trial-gate`. | | `402` | Varies | The account has no plan, its subscription is not active, or its wallet has no balance left. The `type` ends in `payment-required`. | | `403` | `Contact has Do Not Disturb (DND) on` | A contact with this number has DND on, the contact found by this email has DND on, or the number is on the opt-out list. No call was placed. | | `403` | `Contact processing is restricted (GDPR Art.18)` | The contact's data is restricted. No call was placed. | | `403` | `Could not verify Do Not Disturb status` | Nedzo couldn't check the opt-out list, so it placed no call. Try again. | | `403` | `Your plan does not include calls.` | Your plan does not include calls. | | `404` | `Agent not found` | The agent is not in the workspace, or it was deleted. | | `404` | `Contact not found` | The contact is not in the workspace. | | `429` | Varies | The plan's concurrent call limit is reached, or the account is over its write rate. No call was placed. Read `Retry-After`. | | `500` | `Could not look up the contact for this number. Please try again.` | Nedzo couldn't read the contacts on this number, so it placed no call. Try again. | | `500` | `Could not look up the contact for this email. Please try again.` | Nedzo couldn't read the contact with this email, so it placed no call. Try again. | | `500` | `Could not check this number against the Do Not Disturb list. Please try again.` | Nedzo couldn't check DND, so it placed no call. Try again. | | `500` | `Could not check this contact against the Do Not Disturb list. Please try again.` | Nedzo couldn't check DND, so it placed no call. Try again. | | `503` | `Could not check the concurrent call limit. Please retry shortly.` | Nedzo couldn't count the calls in progress. No call was placed. Read `Retry-After`. | | `503` | `We couldn't check this workspace's limits just now. Try again in a moment.` | Nedzo couldn't check the workspace's limits. No call was placed. | ```bash Call Contact theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/call \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "123e4567-e89b-12d3-a456-426614174000", "type": "contact", "contactId": "456e4567-e89b-12d3-a456-426614174000" }' ``` ```bash Call Phone Number theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/call \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "123e4567-e89b-12d3-a456-426614174000", "type": "phoneNumber", "phoneNumber": "+14155551234", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "businessName": "Acme Inc", "customFields": { "Lead Source": "Website", "Company Size": "50-100" } }' ``` ```json Response theme={null} { "conversationId": "123e4567-e89b-12d3-a456-426614174000" } ``` ```json 402 Trial theme={null} { "type": "https://api.nedzo.ai/errors/outbound-trial-gate", "title": "Payment Required", "status": 402, "detail": "Outbound goes live when your trial converts. Go live now to start calling, texting, and emailing contacts." } ``` # Send an email Source: https://docs.nedzo.ai/api-reference/conversations/send-email POST /conversations/email Start an email conversation with a contact or address, or reply in one. Send `conversationId` to reply in a conversation. Send `contactId` or `email` to start one. Send exactly one of the three. Every plan with email can use this endpoint. The workspace needs a verified sending domain. Without one, the send is refused with `409`. See [Email](/integrations/email) to set up your domain. Every send checks Do Not Disturb (DND) first. If the contact has DND on, or the address is on the opt-out list, the send is refused with `403` and nothing is sent. If DND can't be checked, the send is refused too. Workspace UUID (required for account API keys, optional for workspace API keys) Reply in this conversation. It must be an email conversation in the workspace. Start a conversation with this contact Start a conversation with this address. Nedzo finds the contact with this address, or makes one. Name for a contact this send makes. Ignored when the contact exists. The subject line. A reply keeps the thread's subject when you leave this out. The plain text body An HTML body. Nedzo cleans it before sending. `content` is the plain text version. A reply to a conversation that has ended goes into a new conversation with the same contact. The response names the conversation the message went into. ## When `messageId` is null Sometimes the message goes out but Nedzo can't save it. The answer is still `201`, and `messageId` is `null`. The contact got the message, so don't send it again. Then `conversationId` is the conversation you sent into. If that conversation had ended, the message may have gone out on a new thread that does not show in the log. ## Errors | Status | When | | - | - | | `400` | The body is not valid, or the conversation is not an email conversation | | `402` | The account has no plan in force, no balance or messages left, the workspace hit its spend limit, or the trial has not converted | | `403` | The contact has DND on, the contact's data is restricted, or your plan does not include email | | `404` | The workspace, contact or conversation was not found | | `409` | The workspace has no verified sending domain, or a send to this contact is already in progress | | `502` | The message could not be sent. Check the conversation before you try again, in case it went out. | | `503` | Nedzo couldn't check the workspace's limits. Nothing was sent. Try again in a moment. | ```bash Start theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/email \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "subject": "Your appointment", "content": "Hi Jane, your appointment is confirmed for 3pm tomorrow." }' ``` ```json 201 theme={null} { "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "messageId": "4d1c2b3a-5e6f-4a7b-8c9d-0e1f2a3b4c5d" } ``` ```json 409 No domain theme={null} { "type": "https://api.nedzo.ai/errors/conflict", "title": "Conflict", "status": 409, "detail": "Email needs a verified sending domain. Verify your domain in Settings, then try again." } ``` # Reply on Instagram Source: https://docs.nedzo.ai/api-reference/conversations/send-instagram POST /conversations/instagram Reply in an Instagram conversation, within 24 hours of the contact's last message. Instagram only lets a business answer a person who wrote first. So this endpoint replies in a conversation (`conversationId`) and can't start one. You can reply within 24 hours of the contact's last message. After that, the send is refused with `403` until they write again. Plans with Instagram (Pro and up) can use this endpoint. On other plans it returns `403`. Every send checks Do Not Disturb (DND) first. If the contact has DND on, the send is refused with `403` and nothing is sent. If DND can't be checked, the send is refused too. Workspace UUID (required for account API keys, optional for workspace API keys) Reply in this conversation. It must be an Instagram conversation in the workspace. The text ## When `messageId` is null Sometimes the message goes out but Nedzo can't save it. The answer is still `201`, and `messageId` is `null`. The contact got the message, so don't send it again. Then `conversationId` is the conversation you sent into. If that conversation had ended, the message may have gone out on a new thread that does not show in the log. ## Errors | Status | When | | - | - | | `400` | The body is not valid, or the conversation is not an Instagram conversation | | `402` | The account has no plan in force, no balance or messages left, the workspace hit its spend limit, or the trial has not converted | | `403` | The contact has DND on, the contact's data is restricted, your plan does not include Instagram, or the 24-hour window has closed | | `404` | The workspace or conversation was not found | | `429` | Too many messages in a short time. Wait, then send again. | | `502` | The message could not be sent. Check the conversation before you try again, in case it went out. | | `503` | Nedzo couldn't check the workspace's limits. Nothing was sent. Try again in a moment. | ```bash Reply theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/instagram \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "content": "Thanks for reaching out! We are open until 6pm today." }' ``` ```json 201 theme={null} { "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "messageId": "4d1c2b3a-5e6f-4a7b-8c9d-0e1f2a3b4c5d" } ``` # Reply on Messenger Source: https://docs.nedzo.ai/api-reference/conversations/send-messenger POST /conversations/messenger Reply in a Messenger conversation, within 24 hours of the contact's last message. Messenger only lets a business answer a person who wrote first. So this endpoint replies in a conversation (`conversationId`) and can't start one. You can reply within 24 hours of the contact's last message. After that, the send is refused with `403` until they write again. Plans with Messenger (Pro and up) can use this endpoint. On other plans it returns `403`. Every send checks Do Not Disturb (DND) first. If the contact has DND on, the send is refused with `403` and nothing is sent. If DND can't be checked, the send is refused too. Workspace UUID (required for account API keys, optional for workspace API keys) Reply in this conversation. It must be a Messenger conversation in the workspace. The text ## When `messageId` is null Sometimes the message goes out but Nedzo can't save it. The answer is still `201`, and `messageId` is `null`. The contact got the message, so don't send it again. Then `conversationId` is the conversation you sent into. If that conversation had ended, the message may have gone out on a new thread that does not show in the log. ## Errors | Status | When | | - | - | | `400` | The body is not valid, or the conversation is not a Messenger conversation | | `402` | The account has no plan in force, no balance or messages left, the workspace hit its spend limit, or the trial has not converted | | `403` | The contact has DND on, the contact's data is restricted, your plan does not include Messenger, or the 24-hour window has closed | | `404` | The workspace or conversation was not found | | `429` | Too many messages in a short time. Wait, then send again. | | `502` | The message could not be sent. Check the conversation before you try again, in case it went out. | | `503` | Nedzo couldn't check the workspace's limits. Nothing was sent. Try again in a moment. | ```bash Reply theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/messenger \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "content": "Thanks for reaching out! We are open until 6pm today." }' ``` ```json 201 theme={null} { "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "messageId": "4d1c2b3a-5e6f-4a7b-8c9d-0e1f2a3b4c5d" } ``` # Send an SMS Source: https://docs.nedzo.ai/api-reference/conversations/send-sms POST /conversations/sms Start an SMS conversation with a contact or phone number, or reply in one. Send `conversationId` to reply in a conversation. Send `contactId` or `phone` to start one. Send exactly one of the three. Every plan with SMS can use this endpoint. Every send checks Do Not Disturb (DND) first. If the contact has DND on, or the number is on the opt-out list, the send is refused with `403` and nothing is sent. If DND can't be checked, the send is refused too. Workspace UUID (required for account API keys, optional for workspace API keys) Reply in this conversation. It must be an SMS conversation in the workspace. Start a conversation with this contact Start a conversation with this number, in E.164 format (for example `+14155551234`). Nedzo finds the contact with this number, or makes one. Name for a contact this send makes. Ignored when the contact exists. The text, up to 1,600 characters Send from this number in the workspace. A reply uses the conversation's number by default. A new conversation uses the workspace's first active number. A reply to a conversation that has ended goes into a new conversation with the same contact. The response names the conversation the message went into. ## When `messageId` is null Sometimes the message goes out but Nedzo can't save it. The answer is still `201`, and `messageId` is `null`. The contact got the message, so don't send it again. Then `conversationId` is the conversation you sent into. If that conversation had ended, the message may have gone out on a new thread that does not show in the log. ## Errors | Status | When | | - | - | | `400` | The body is not valid, or the conversation is not an SMS conversation | | `402` | The account has no plan in force, no balance or messages left, the workspace hit its spend limit, or the trial has not converted. During a trial you can text a number verified in your workspace. | | `403` | The contact has DND on, the contact's data is restricted, or your plan does not include SMS | | `404` | The workspace, contact or conversation was not found | | `409` | A send to this contact is already in progress | | `429` | Too many messages in a short time. Wait, then send again. | | `502` | The message could not be sent. Check the conversation before you try again, in case it went out. | | `503` | Nedzo couldn't check the workspace's limits. Nothing was sent. Try again in a moment. | ```bash Start theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/sms \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "+14155551234", "name": "Jane Doe", "content": "Hi Jane, your appointment is confirmed for 3pm tomorrow." }' ``` ```bash Reply theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/sms \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "content": "See you then!" }' ``` ```json 201 theme={null} { "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "messageId": "4d1c2b3a-5e6f-4a7b-8c9d-0e1f2a3b4c5d" } ``` ```json 403 DND theme={null} { "type": "https://api.nedzo.ai/errors/forbidden", "title": "Forbidden", "status": 403, "detail": "Contact has Do Not Disturb (DND) on" } ``` # Send a WhatsApp message Source: https://docs.nedzo.ai/api-reference/conversations/send-whatsapp POST /conversations/whatsapp Start a WhatsApp conversation with a contact or phone number, or reply in one. Send `conversationId` to reply in a conversation. Send `contactId` or `phone` to start one. Send exactly one of the three. Plans with WhatsApp (Pro and up) can use this endpoint. On other plans it returns `403`. WhatsApp lets you send text only within 24 hours of the contact's last message. Outside that window, send an approved template with `templateId`. A text send outside the window is refused with `400`, and nothing is sent. See [Reply windows](/integrations/whatsapp#reply-windows). Every send checks Do Not Disturb (DND) first. If the contact has DND on, or the number is on the opt-out list, the send is refused with `403` and nothing is sent. If DND can't be checked, the send is refused too. Workspace UUID (required for account API keys, optional for workspace API keys) Reply in this conversation. It must be a WhatsApp conversation in the workspace. Start a conversation with this contact. The contact needs a phone number. Start a conversation with this number, in E.164 format. Nedzo finds the contact with this number, or makes one. Name for a contact this send makes. Ignored when the contact exists. The text. Send `content`, `templateId`, or both. With a template, `content` is the text Nedzo shows for the message in Unibox. An approved WhatsApp template from your workspace's template list Values for the template's numbered placeholders, for example `{ "1": "Jane" }`. Needs `templateId`. Start from this WhatsApp sender. By default Nedzo uses the workspace's first active sender. Ignored on a reply. ## When `messageId` is null Sometimes the message goes out but Nedzo can't save it. The answer is still `201`, and `messageId` is `null`. The contact got the message, so don't send it again. Then `conversationId` is the conversation you sent into. If that conversation had ended, the message may have gone out on a new thread that does not show in the log. ## Errors | Status | When | | - | - | | `400` | The body is not valid, the conversation is not a WhatsApp conversation, the workspace has no active WhatsApp sender, or a template is needed because the 24-hour window has closed | | `402` | The account has no plan in force, no balance or messages left, the workspace hit its spend limit, or the trial has not converted. During a trial you can message a number verified in your workspace. | | `403` | The contact has DND on, the contact's data is restricted, or your plan does not include WhatsApp | | `404` | The workspace, contact or conversation was not found | | `409` | A send to this contact is already in progress | | `429` | Too many messages in a short time. Wait, then send again. | | `502` | The message could not be sent. Check the conversation before you try again, in case it went out. | | `503` | Nedzo couldn't check the workspace's limits. Nothing was sent. Try again in a moment. | ```bash Template theme={null} curl -X POST https://api.nedzo.ai/v1/conversations/whatsapp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contactId": "456e4567-e89b-12d3-a456-426614174000", "templateId": "HXb5b62575e6e4ff6129ad7c8efe1f983e", "templateVariables": { "1": "Jane" } }' ``` ```json 201 theme={null} { "conversationId": "9b2f6c1e-3a4d-4e5f-8a9b-0c1d2e3f4a5b", "messageId": "4d1c2b3a-5e6f-4a7b-8c9d-0e1f2a3b4c5d" } ``` ```json 400 Template needed theme={null} { "type": "https://api.nedzo.ai/errors/validation-error", "title": "Validation Error", "status": 400, "detail": "A WhatsApp template is needed outside the 24-hour window. Send templateId." } ``` # API reference Source: https://docs.nedzo.ai/api-reference/introduction Complete reference for the Nedzo REST API. Covers authentication, base URL, request formats, error codes, pagination, and available endpoints. The Nedzo API is organized around REST. It uses standard HTTP methods, returns JSON responses, and uses standard HTTP status codes. ## Base URL ``` https://api.nedzo.ai/v1 ``` ## Authentication All API requests require a Bearer token in the `Authorization` header: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` See [Authentication](/authentication) for more details. If you're using an **account-scoped** API key, most endpoints require a `workspaceId` in the request body or query string. Workspace-scoped keys infer it automatically. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope) for details on which to use and how to find your workspace ID. ## Plan access Every paid plan can use these endpoints: * Contacts (`/v1/contacts`) * Make a call (`POST /v1/call`, deprecated: use `POST /v1/conversations/call`) * Conversation logs (`GET /v1/conversations` and `GET /v1/conversations/{id}`) The send endpoints follow the channels on your plan, not the tier: * Calls, SMS and email (`POST /v1/conversations/call`, `/sms` and `/email`) on every plan * WhatsApp, Instagram and Messenger (`POST /v1/conversations/whatsapp`, `/instagram` and `/messenger`) on Pro and up A send on a channel your plan does not include returns a `403` that names the channel, for example `"detail": "Your plan does not include WhatsApp."`. Everything else is for VIP only. That covers agents and their knowledge, actions and settings, custom actions, MCP servers, calendars, phone numbers, the knowledge library, workflows, campaigns, templates, workspaces and workspace billing. Stopping a contact in a campaign is VIP only too, under both its new path and the old `/v1/outbound-campaigns` path. Each of those pages carries a "VIP plan only" note. A new endpoint is VIP only unless its page says every plan can use it. On other plans these endpoints return a `403`: ```json theme={null} { "type": "https://api.nedzo.ai/errors/forbidden", "title": "Forbidden", "status": 403, "detail": "This API is available on the VIP plan. Your plan can use the Contacts, Call and conversation log APIs.", "instance": "/v1/agents", "requiredPlan": "vip", "feature": "fullApiAccess" } ``` To use them, move to VIP. See [Plans](/billing/plans). Every endpoint returns a `402` when the account has no plan, or its subscription is not active. ## Request Format For `POST`, `PUT`, and `PATCH` requests, send JSON in the request body: ```bash theme={null} curl -X POST https://api.nedzo.ai/v1/agents \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "My Agent", "workspaceId": "789e4567-e89b-12d3-a456-426614174000"}' ``` ## Response Format All responses are JSON. Successful responses return the requested data: ```json theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "My Agent", "createdAt": "2024-01-15T10:30:00Z" } ``` ## Errors Errors follow the [RFC 7807](https://tools.ietf.org/html/rfc7807) Problem Details format: ```json theme={null} { "type": "https://api.nedzo.ai/errors/not-found", "title": "Not Found", "status": 404, "detail": "Agent not found" } ``` ### HTTP Status Codes | Code | Description | | - | - | | `200` | Success | | `201` | Created | | `400` | Bad Request - Invalid parameters | | `401` | Unauthorized - Invalid or missing API key | | `403` | Forbidden - Not allowed for this key or account, for example an endpoint your plan does not include. See [Plan access](#plan-access) | | `404` | Not Found - Resource doesn't exist | | `409` | Conflict - Resource already exists or conflicts with existing state | | `422` | Validation Error - Request failed validation | | `429` | Too Many Requests - Rate limited | | `500` | Internal Server Error | | `502` | Bad Gateway - Upstream service error | ## Pagination List endpoints support pagination via query parameters: | Parameter | Description | Default | | - | - | - | | `limit` | Number of items per page | Varies by endpoint, see each list page. Contacts: 100, up to 1,000 | | `offset` | Number of items to skip | 0 | Contacts and wallet transactions return `{ items, total, limit, offset, hasMore }`: keep adding `limit` to `offset` while `hasMore` is true. Other list endpoints return an array. ```bash theme={null} GET /v1/contacts?limit=50&offset=100 ``` ## Rate Limiting Write requests on the public API (creating contacts, sending messages, starting calls) run at **1 per second per account**. Every key on the account shares that one rate, account keys and workspace keys together. * **VIP:** writes can queue. If you send 10 in the same second, 1 starts now and the other 9 wait their turn, starting 1 per second. Each waiting request stays open and gets its normal response when it runs. A write that would have to wait more than 9 seconds to start gets a `429`. * **Other plans:** no queue. A second write in the same second gets a `429`. A `429` comes with a `Retry-After` header (in seconds) saying when to retry. Read endpoints (`GET`) are not part of this limit. Starting a call also checks your plan's concurrent call limit. At the limit, the call is not placed and you get a `429` such as `concurrent call limit reached: 5 of 5 in use`, with `Retry-After`. Calls are never queued. If the limit can't be checked, you get a `503` with `Retry-After`, and no call is placed. # Add knowledge Source: https://docs.nedzo.ai/api-reference/knowledge/create POST /knowledge Add a website, a text or a file to the knowledge library of a workspace through the Nedzo REST API. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Add one item to the knowledge library. Set `type` to `website`, `text` or `file`, and send the field for that type. An item does nothing until an agent uses it. To add a file: 1. Get an upload link from [Get a file upload link](/api-reference/knowledge/upload). 2. `PUT` the file to the `uploadUrl`. 3. Send the `fileUrl` and `fileName` here. A `fileUrl` must come from an upload link for the same workspace. A link to a file somewhere else is refused. The file can be up to 5 MB, and `fileName` must have the same extension as the upload. Anyone with the `fileUrl` can open the file. When we refuse a file because it is over 5 MB, we remove the uploaded file. For any other refusal, the file stays, so you can fix the request and send it again with the same `fileUrl`. Your plan sets how many knowledge items the account can have. The count covers every workspace in the account. `workspaceId` is required for **account-scoped** API keys and taken from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Body Parameters The workspace. Required for an account-scoped key. `website`, `text` or `file`. A name for the item, 1 to 255 characters. For a `website` item. A public web page. We add `https://` when you leave it out. A private or local address is refused. For a `text` item. Up to 500,000 characters. For a `file` item. The `fileUrl` from your upload link, after the upload is done. For a `file` item. The file name, with its extension: `pdf`, `docx`, `txt`, `md` or `csv`. ## Errors | Status | When | | - | - | | `400` | A field is missing or does not fit the type, the website address is not allowed, the file type is not supported, the `fileUrl` is not from an upload link for this workspace, `fileName` has a different extension than the upload, no file was uploaded there yet, the file is over 5 MB, or the plan limit is reached. | | `402` | The account has no plan, or its subscription is not active. | | `404` | The workspace is not in your API key's scope. | | `403` | The workspace is locked. The answer has `workspaceLocked: true`. | | `502` | We could not check the plan limit, the workspace or the uploaded file, or its size. Try again. | ```bash Website theme={null} curl -X POST "https://api.nedzo.ai/v1/knowledge" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "website", "name": "Pricing page", "websiteUrl": "https://example.com/pricing" }' ``` ```bash Text theme={null} curl -X POST "https://api.nedzo.ai/v1/knowledge" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "text", "name": "Opening hours", "textContent": "We are open 9 to 5, Monday to Friday." }' ``` ```bash File theme={null} curl -X POST "https://api.nedzo.ai/v1/knowledge" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "file", "name": "Price list", "fileUrl": "FILE_URL_FROM_THE_UPLOAD_LINK", "fileName": "price-list.pdf" }' ``` ```json Response (201) theme={null} { "id": "33333333-3333-4333-8333-333333333333", "workspaceId": "22222222-2222-4222-8222-222222222222", "name": "Opening hours", "type": "text", "websiteUrl": null, "fileUrl": null, "fileName": null, "textContent": "We are open 9 to 5, Monday to Friday.", "agents": [], "createdAt": "2026-10-11T10:00:00Z", "updatedAt": "2026-10-11T10:00:00Z" } ``` # Delete knowledge Source: https://docs.nedzo.ai/api-reference/knowledge/delete DELETE /knowledge/{id} Delete a knowledge item and remove it from every agent that uses it, through the Nedzo REST API. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Delete a knowledge item. Every agent that uses it stops using it. If we can't remove it from every agent, you get a `502`. The item stays in the library, but some of its agents may have stopped using it already. Send the delete again to finish. Answers `204` with no body. ## Path Parameters The knowledge item ID. ## Errors | Status | When | | - | - | | `402` | The account has no plan, or its subscription is not active. | | `404` | The item does not exist, was already deleted, or is not in your API key's scope. | | `403` | The workspace is locked. The answer has `workspaceLocked: true`. | | `502` | We could not finish the delete. The item may still be in the library, and some agents may have stopped using it. Send the delete again to finish. | ```bash theme={null} curl -X DELETE "https://api.nedzo.ai/v1/knowledge/33333333-3333-4333-8333-333333333333" \ -H "Authorization: Bearer YOUR_API_KEY" ``` # Get knowledge Source: https://docs.nedzo.ai/api-reference/knowledge/get GET /knowledge/{id} Get one knowledge item, with its text and the agents that use it, through the Nedzo REST API. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Get one knowledge item. A text item includes its `textContent`. ## Path Parameters The knowledge item ID. ## Errors | Status | When | | - | - | | `402` | The account has no plan, or its subscription is not active. | | `404` | The item does not exist, was deleted, or is not in your API key's scope. | ```bash theme={null} curl "https://api.nedzo.ai/v1/knowledge/33333333-3333-4333-8333-333333333333" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (200) theme={null} { "id": "33333333-3333-4333-8333-333333333333", "workspaceId": "22222222-2222-4222-8222-222222222222", "name": "Opening hours", "type": "text", "websiteUrl": null, "fileUrl": null, "fileName": null, "textContent": "We are open 9 to 5, Monday to Friday.", "agents": [ { "id": "44444444-4444-4444-8444-444444444444", "name": "Front desk", "type": "voice" } ], "createdAt": "2026-10-11T10:00:00Z", "updatedAt": "2026-10-11T10:00:00Z" } ``` # List knowledge Source: https://docs.nedzo.ai/api-reference/knowledge/list GET /knowledge List the knowledge library of a workspace through the Nedzo REST API, with the agents that use each item. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). List the knowledge items in a workspace, newest first. Each item shows the agents that use it. The text of a text item is not in the list. Get the item to read it. `workspaceId` is required for **account-scoped** API keys and taken from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Query Parameters The workspace. Required for an account-scoped key. Only items of this type: `website`, `text` or `file`. Items per page, from 1 to 100. Items to skip, up to 100,000. ## Response The items on this page. Each has `id`, `workspaceId`, `name`, `type`, `websiteUrl`, `fileUrl`, `fileName`, `agents`, `createdAt` and `updatedAt`. The agents that use the item. Each has `id`, `name` and `type` (`voice`, `chat` or `web`). How many items match, across all pages. `true` when there is another page. ```bash theme={null} curl "https://api.nedzo.ai/v1/knowledge?limit=50&offset=0" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (200) theme={null} { "items": [ { "id": "33333333-3333-4333-8333-333333333333", "workspaceId": "22222222-2222-4222-8222-222222222222", "name": "Pricing page", "type": "website", "websiteUrl": "https://example.com/pricing", "fileUrl": null, "fileName": null, "agents": [ { "id": "44444444-4444-4444-8444-444444444444", "name": "Front desk", "type": "voice" } ], "createdAt": "2026-10-11T10:00:00Z", "updatedAt": "2026-10-11T10:00:00Z" } ], "total": 1, "limit": 50, "offset": 0, "hasMore": false } ``` # Update knowledge Source: https://docs.nedzo.ai/api-reference/knowledge/update PATCH /knowledge/{id} Rename a knowledge item or change its content through the Nedzo REST API. Agents that use it get the new content. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Change the name, or the content that fits the item's type. You can't change the type. Send only the fields you want to change. When the content changes, we read the new content first. If we can't read it, nothing changes. Then every agent that uses the item gets the new content. When we refuse a new file because it is over 5 MB, we remove the uploaded file. For any other refusal, the file stays, so you can fix the request and send it again. ## Path Parameters The knowledge item ID. ## Body Parameters A new name, 1 to 255 characters. `website` items only. A new public web page. `text` items only. New text, up to 500,000 characters. `file` items only. The `fileUrl` from a new upload link. Send it with `fileName`. `file` items only. Send it with `fileUrl`. ## Errors | Status | When | | - | - | | `400` | No field was sent, a field does not fit the item's type, `fileUrl` and `fileName` were not sent together, the `fileUrl` is not from an upload link for this workspace, no file was uploaded there yet, the new file is over 5 MB or has a different extension than `fileName`, or the new content can't be used. | | `402` | The account has no plan, or its subscription is not active. | | `404` | The item does not exist, was deleted, or is not in your API key's scope. | | `403` | The workspace is locked. The answer has `workspaceLocked: true`. | | `502` | We could not check the uploaded file or its size, or we could not read the new content. Nothing changed. Or the item was saved but some agents did not get the new content. The message says which. In the last case, send the same update again. | ```bash theme={null} curl -X PATCH "https://api.nedzo.ai/v1/knowledge/33333333-3333-4333-8333-333333333333" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "textContent": "We are open 8 to 6, Monday to Saturday." }' ``` # Get a file upload link Source: https://docs.nedzo.ai/api-reference/knowledge/upload POST /knowledge/uploads Get a link to upload a file for the knowledge library through the Nedzo REST API, so you don't have to host the file yourself. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Get a link to upload one file. Then: 1. `PUT` the file bytes to `uploadUrl`, with the `Content-Type` header set to `contentType`. 2. Add the file with [Add knowledge](/api-reference/knowledge/create), using the `fileUrl` and `fileName` from this answer. Each link puts the file at a new address, so it can't replace another file. The link works for 2 hours. A file can be up to 5 MB. That is the most we can read. A bigger file uploads, but [Add knowledge](/api-reference/knowledge/create) refuses it and removes it. The link fixes the address, not the file type. Send the `contentType` from this answer. When you add the file, `fileName` must have the same extension as the upload. Anyone with the `fileUrl` can open the file. | Extension | `contentType` | | - | - | | `pdf` | `application/pdf` | | `docx` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | | `txt` | `text/plain` | | `md` | `text/markdown` | | `csv` | `text/csv` | `workspaceId` is required for **account-scoped** API keys and taken from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Body Parameters The workspace. Required for an account-scoped key. The file name, with its extension. The file type from the table above. It must match the extension. ## Errors | Status | When | | - | - | | `400` | The file type is not supported, or `contentType` does not match the extension. | | `402` | The account has no plan, or its subscription is not active. | | `404` | The workspace is not in your API key's scope. | | `403` | The workspace is locked. The answer has `workspaceLocked: true`. | | `502` | We could not check the workspace or create a link. Try again. | ```bash theme={null} curl -X POST "https://api.nedzo.ai/v1/knowledge/uploads" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "fileName": "price-list.pdf", "contentType": "application/pdf" }' # Then upload the file to the uploadUrl from the answer curl -X PUT "UPLOAD_URL" \ -H "Content-Type: application/pdf" \ --data-binary @price-list.pdf ``` ```json Response (201) theme={null} { "uploadUrl": "https://...", "method": "PUT", "fileUrl": "https://...", "fileName": "price-list.pdf", "contentType": "application/pdf", "maxBytes": 5242880, "expiresAt": "2026-10-11T12:00:00Z" } ``` # Buy a phone number Source: https://docs.nedzo.ai/api-reference/phone-numbers/buy POST /phone-numbers Buy a local US or Canada number for a workspace. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). The number is billed every month until you release it. Search first, then buy one of the numbers the search gave you. The number in E.164 format, for example `+14155551234`. US and Canada local numbers only. Must be `true`. You agree to pay for this number every month until you release it. We record your API key, your IP address and your user agent with the purchase. Workspace UUID. Required for an account API key. A workspace API key uses its own workspace. ## Retries You can send the same request again. If the workspace already owns the number, you get `200` with that number and nothing is charged. While a purchase of the number is still running, you get `409`. If an earlier purchase of the number may have taken money and never finished, you also get `409`, at any age. The message has a reference. Contact support with it before you buy the number again. When a purchase fails after the number was held for you, the number stays held for about 30 minutes. A retry before then is refused. Try again after that, or pick another number. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/phone-numbers" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phoneNumber": "+14155551234", "consent": true }' ``` ```json Response theme={null} { "id": "4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c", "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "number": "+14155551234", "status": "active", "type": "inbound_outbound", "friendlyName": null, "inboundAgentId": null, "chatAgentId": null, "fallbackEnabled": false, "fallbackNumber": null, "fallbackRingDuration": 20, "activeHours": null, "monthlyPriceCents": 99, "purchasedAt": "2026-10-11T12:00:00Z", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ``` ## Errors | Status | When | | - | - | | `200` | The workspace already owns this number. Nothing was charged | | `400` | The number is not a US or Canada local number, `consent` is not `true`, the number is no longer available, or the workspace reached its monthly spending limit | | `400` | `Insufficient funds to complete purchase`: the workspace wallet does not have enough money for this number | | `400` | `Too many reservation requests. Please try again later.`: wait a little, then try again | | `400` | `This number is temporarily unavailable. Please try another number.` | | `400` | `Phone number price exceeds maximum allowed`: this number costs more than we allow. Pick another one | | `400` | `Purchase already in progress. Please wait.`: a purchase of this number is still running | | `400` | `The phone number service refused this request. Check the number and try again.` | | `402` | The plan cannot buy numbers yet: a trial, an unpaid plan or no plan | | `404` | The workspace is not found, or not in this API key's scope | | `409` | A purchase of this number is already running, or an earlier one may have taken money and is still being settled. The message says which | | `500` | The purchase failed. The message says what happened to any charge, and whether the number is still held | | `502` | The phone number service did not answer. Try again | | `503` | A check could not run. Nothing was bought. The message says when you can try again | # Get a phone number Source: https://docs.nedzo.ai/api-reference/phone-numbers/get GET /phone-numbers/{id} Get one phone number. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Phone number UUID ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/phone-numbers/4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c", "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "number": "+14155551234", "status": "active", "type": "inbound_outbound", "friendlyName": "Main line", "inboundAgentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "chatAgentId": null, "fallbackEnabled": false, "fallbackNumber": null, "fallbackRingDuration": 20, "activeHours": null, "monthlyPriceCents": 99, "purchasedAt": "2026-10-11T12:00:00Z", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ``` See [List phone numbers](/api-reference/phone-numbers/list#fields) for what each field means. A number outside this API key's scope returns `404`. # List phone numbers Source: https://docs.nedzo.ai/api-reference/phone-numbers/list GET /phone-numbers List the phone numbers a workspace owns, newest first. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID. Required for an account API key. A workspace API key uses its own workspace. Numbers per page, 1 to 1,000. How many numbers to skip, up to 100,000. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/phone-numbers?limit=50" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "id": "4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c", "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "number": "+14155551234", "status": "active", "type": "inbound_outbound", "friendlyName": "Main line", "inboundAgentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "chatAgentId": null, "fallbackEnabled": false, "fallbackNumber": null, "fallbackRingDuration": 20, "activeHours": null, "monthlyPriceCents": 99, "purchasedAt": "2026-10-11T12:00:00Z", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ], "total": 1, "limit": 50, "offset": 0, "hasMore": false } ``` ## Fields | Field | Meaning | | - | - | | `status` | `pending`, `active`, `payment_failed` or `activation_failed` | | `type` | `inbound_outbound` or `outbound_only` | | `inboundAgentId` | The voice agent that answers and calls from this number | | `chatAgentId` | The chat agent that answers texts on this number | | `fallbackEnabled` | Ring a person first, then the agent | | `fallbackNumber` | The person to ring first | | `fallbackRingDuration` | Seconds to ring the person before the agent answers | | `activeHours` | When to ring the person first. `null` uses the workspace office hours | | `monthlyPriceCents` | What the number costs each month, in cents | # Release a phone number Source: https://docs.nedzo.ai/api-reference/phone-numbers/release DELETE /phone-numbers/{id} Release a number. It leaves the workspace and its monthly billing stops. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). This cannot be undone. Agents on the number stop answering and calling from it. Phone number UUID ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/phone-numbers/4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c" \ -H "Authorization: Bearer YOUR_API_KEY" ``` The answer is `204` with no body. A number that was already released returns `404`. ## Errors | Status | When | | - | - | | `404` | `Phone number not found`: the number does not exist, or it is not in this API key's scope | | `500` | The number could not be released. It is still in the workspace. Try again | # Search numbers Source: https://docs.nedzo.ai/api-reference/phone-numbers/search POST /phone-numbers/search Find local US and Canada numbers you can buy, with the monthly price. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). `US` or `CA`. A 3-digit area code, for example `415`. Digits the number must contain, up to 10. How many numbers to return, 1 to 100. Only local numbers are for sale. Toll-free and premium numbers are not. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/phone-numbers/search" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "countryCode": "US", "areaCode": "415", "limit": 5 }' ``` ```json Response theme={null} { "items": [ { "phoneNumber": "+14155551234", "nationalFormat": "(415) 555-1234", "region": { "city": "San Francisco", "state": "CA" }, "monthlyPriceCents": 99 } ], "total": 1 } ``` ## Errors | Status | When | | - | - | | `400` | A field is not valid | | `502` | The phone number service did not answer. Try again | # Update a phone number Source: https://docs.nedzo.ai/api-reference/phone-numbers/update PATCH /phone-numbers/{id} Change a number's label, its ring-a-person-first settings, or its active hours. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). To change which agent uses the number, use [Give an agent a phone number](/api-reference/agents/assign-phone-number). Phone number UUID Your label for the number, up to 100 characters. Ring a person first, then the agent. The person to ring first, in E.164 format. Seconds to ring the person before the agent answers, 1 to 60. When to ring the person first, per weekday. `null` uses the workspace office hours. Send at least one field. Any other field returns `400`. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/phone-numbers/4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "friendlyName": "Main line" }' ``` ```json Response theme={null} { "id": "4d1f6b52-8c0e-4a51-9b7d-2f3c4e5a6b7c", "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "number": "+14155551234", "status": "active", "type": "inbound_outbound", "friendlyName": "Main line", "inboundAgentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "chatAgentId": null, "fallbackEnabled": false, "fallbackNumber": null, "fallbackRingDuration": 20, "activeHours": null, "monthlyPriceCents": 99, "purchasedAt": "2026-10-11T12:00:00Z", "createdAt": "2026-10-11T12:00:00Z", "updatedAt": "2026-10-11T12:00:00Z" } ``` # Create template Source: https://docs.nedzo.ai/api-reference/templates/create POST /templates Create a new template from an agent, workflow, or entire workspace via the Nedzo REST API. Snapshots configuration for sharing or reuse. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Create a new template. Specify `templateType` as `"agent"`, `"workflow"`, or `"workspace"`. For `agent`/`workflow` provide the corresponding `agentId`/`workflowId`. For `workspace`, the entire workspace is snapshotted (all agents, workflows, custom fields, and tags; contacts and conversations are excluded). `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Body Parameters Source workspace. For `agent`/`workflow` templates: the workspace containing the source entity. For `workspace` templates: the workspace to snapshot. Required for account-scoped API keys, inferred from workspace-scoped keys if omitted. Type of template to create. One of `agent`, `workflow`, or `workspace`. Agent ID to create template from. Required when `templateType` is `"agent"`. Workflow ID to create template from. Required when `templateType` is `"workflow"`. Template name (1-255 characters). Template description (max 1000 characters). ```bash Agent theme={null} curl -X POST "https://api.nedzo.ai/v1/templates" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "templateType": "agent", "agentId": "456e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding" }' ``` ```bash Workflow theme={null} curl -X POST "https://api.nedzo.ai/v1/templates" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "templateType": "workflow", "workflowId": "456e4567-e89b-12d3-a456-426614174000", "name": "Lead Qualification Workflow" }' ``` ```bash Workspace theme={null} curl -X POST "https://api.nedzo.ai/v1/templates" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "templateType": "workspace", "name": "Sales Team Starter", "description": "Full workspace setup — agents, workflows, custom fields, and tags" }' ``` ```json Response (201) theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-15T10:30:00Z" } ``` # Delete template Source: https://docs.nedzo.ai/api-reference/templates/delete DELETE /templates/{id} Soft-delete a template by UUID via the Nedzo REST API. The template is hidden from list results but not permanently removed from the database. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Soft-delete a template. The template is not permanently removed but will no longer appear in list results. Returns `204 No Content` on success. Returns `404` if the template does not exist or is already deleted. ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```text Response (204) theme={null} No Content ``` # Duplicate template Source: https://docs.nedzo.ai/api-reference/templates/duplicate POST /templates/{id}/duplicate Duplicate an existing template via the Nedzo REST API. The copy is created with private visibility and a "(Copy)" suffix appended to the name. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Create a copy of a template. The duplicate gets `" (Copy)"` appended to its name and is always set to private visibility regardless of the original. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000/duplicate" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (201) theme={null} { "id": "def45678-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent (Copy)", "description": "A voice agent template for onboarding", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-16T09:15:00Z", "updatedAt": "2026-01-16T09:15:00Z" } ``` # Get template Source: https://docs.nedzo.ai/api-reference/templates/get GET /templates/{id} Retrieve a template by UUID via the Nedzo REST API. Returns metadata and the full decompressed snapshot including agents, workflows, and tags. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Retrieve a template by its UUID. Returns metadata plus the full decompressed snapshot (agents, workflows, custom fields, and tags). ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-15T10:30:00Z", "snapshot": { "schemaVersion": "1.0.0", "exportedAt": "2026-01-15T10:30:00Z", "sourcePlatformVersion": "1.0.0", "content": { "agents": [ { "original_id": "456e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding", "agent_type": "Inbound Voice", "prompt": "You are a helpful onboarding assistant...", "opening_line": "Hello! Welcome to our platform.", "language": "english" } ] }, "metadata": { "originalIds": { "456e4567-e89b-12d3-a456-426614174000": "agent" }, "requiredIntegrations": ["ghl"] } } } ``` # Import template Source: https://docs.nedzo.ai/api-reference/templates/import POST /templates/{id}/import Import a public or private template into a workspace via the Nedzo REST API. The import is queued and returns an importId to track the job. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Import a template into a workspace. The import is queued for processing and completes quickly; the returned `importId` identifies the job. Public templates can be imported by any account. Private templates can only be imported by the owning account. `workspaceId` is always required for this endpoint — it identifies the target workspace to import into and cannot be inferred from the API key. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Body Parameters Target workspace to import the template into. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000/import" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"workspaceId": "789e4567-e89b-12d3-a456-426614174000"}' ``` ```json Response (201) theme={null} { "importId": "abc12345-e89b-12d3-a456-426614174000", "status": "queued", "validation": { "valid": true, "itemCounts": { "agents": 1, "workflows": 0, "customFields": 0, "tags": 0 }, "warnings": [], "errors": [] }, "message": "Import queued successfully." } ``` # List templates Source: https://docs.nedzo.ai/api-reference/templates/list GET /templates Retrieve all templates owned by your account via the Nedzo REST API. Filter by template type and visibility, with limit and offset pagination. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). List templates owned by your account. Supports filtering by `templateType` and `visibility`, with pagination via `limit`/`offset`. ## Query Parameters Filter by template type. One of `agent`, `workflow`, or `workspace`. Filter by visibility. One of `public` or `private`. Maximum number of templates to return (1-100). Number of templates to skip for pagination. ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/templates?templateType=agent&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding new customers", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-15T10:30:00Z" } ] ``` # Update template Source: https://docs.nedzo.ai/api-reference/templates/update PATCH /templates/{id} Update a template's name, description, or visibility setting via the Nedzo REST API. Only the fields you provide in the request body are changed. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Update template metadata. Only provided fields will be updated. At least one field must be provided. ## Body Parameters Template name (1-255 characters). Template description. Set to `null` to clear. Template visibility. One of `public` or `private`. Public templates can be previewed without authentication and imported by any account. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Template Name", "description": "Updated description", "visibility": "public" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Updated Template Name", "description": "Updated description", "templateType": "agent", "visibility": "public", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-16T08:00:00Z" } ``` # Create a workflow Source: https://docs.nedzo.ai/api-reference/workflows/create POST /workflows Create a workflow with a trigger and steps. It is not live until you publish it. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Create a workflow. A new workflow is on but not published, so it does not run yet. When the steps are ready, [publish it](/api-reference/workflows/publish). ## Body The workspace. Required for an account key. A workspace key uses its own workspace. The workflow's name, up to 200 characters. The trigger and the steps, up to 500. See [Steps](/api-reference/workflows/replace#steps) for how they fit together and the rules for webhook headers and DND. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workflows" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Welcome text", "nodes": [ { "id": "start", "type": "contactCreated", "parentId": null, "config": {} }, { "id": "text", "type": "sendSms", "parentId": "start", "config": { "phoneNumberId": "323e4567-e89b-12d3-a456-426614174000", "message": "Hi {{firstName}}!" } } ] }' ``` ```json 201 theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "223e4567-e89b-12d3-a456-426614174000", "name": "Welcome text", "isPublished": false, "isActive": true, "version": 1, "publishedAt": null, "createdAt": "2026-10-11T09:00:00Z", "updatedAt": "2026-10-11T09:00:00Z", "nodes": [ { "id": "start", "type": "contactCreated", "parentId": null, "pathId": null, "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "text", "type": "sendSms", "parentId": "start", "pathId": null, "config": { "phoneNumberId": "323e4567-e89b-12d3-a456-426614174000", "message": "Hi {{firstName}}!" }, "position": { "x": 0, "y": 0 } } ] } ``` ## Errors | Status | When | | - | - | | `400` | A field is missing or wrong, a step breaks a rule on the [Steps](/api-reference/workflows/replace#steps) list, or an account key sent no `workspaceId`. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP, workflows are not on your plan, or the workspace is locked. | | `404` | The workspace is not in this key's scope. | # Delete a workflow Source: https://docs.nedzo.ai/api-reference/workflows/delete DELETE /workflows/{id} Delete a workflow. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Delete a workflow. It starts no new runs, and its schedule stops. ## Path Parameters The workflow's id. ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` It answers `204` with no body. ## Errors | Status | When | | - | - | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP. | | `404` | No workflow has this id, or it is not in this key's scope. | # Add contacts to a workflow Source: https://docs.nedzo.ai/api-reference/workflows/enroll POST /workflows/{id}/enroll Start a run of a workflow for each contact you name. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Start a run for each contact, whatever the workflow's trigger is. Each run starts at the first step after the trigger. The workflow must be published, on, and have at least one step. We answer `202` when the runs are queued. The steps run after that. Each step that sends a message or makes a call checks DND for the contact when it runs. We skip a contact that is not in this workspace, is deleted, or may not be processed. We skip a contact that is in a run of this workflow that has not finished. ## Path Parameters The workflow's id. ## Body The contacts to add, 1 to 10,000. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000/enroll" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contactIds": ["523e4567-e89b-12d3-a456-426614174000"] }' ``` ```json 202 theme={null} { "enrolled": 1, "skipped": 0, "alreadyInWorkflow": 0 } ``` | Field | What it counts | | - | - | | `enrolled` | Runs started. | | `skipped` | Contacts not in this workspace, deleted, or not allowed to be processed. | | `alreadyInWorkflow` | Contacts in a run of this workflow that has not finished. | ## Errors | Status | When | | - | - | | `400` | `contactIds` is empty or has more than 10,000 ids, or the workflow is not published, not on, has no steps, or has a step that turns DND off. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP, workflows are not on your plan, or the workspace is locked. | | `404` | No workflow has this id, or it is not in this key's scope. | # Get a workflow Source: https://docs.nedzo.ai/api-reference/workflows/get GET /workflows/{id} Get one workflow with its trigger and steps. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Get one workflow with its trigger and steps. A workflow with a schedule trigger has `scheduleInfo`, with the next and last run times. Webhook step headers are write only. A webhook step shows `headerNames` and not the values. ## Path Parameters The workflow's id. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "223e4567-e89b-12d3-a456-426614174000", "name": "Send new leads to my CRM", "isPublished": true, "isActive": true, "version": 2, "publishedAt": "2026-10-11T09:00:00Z", "createdAt": "2026-10-01T09:00:00Z", "updatedAt": "2026-10-11T09:00:00Z", "nodes": [ { "id": "start", "type": "contactCreated", "parentId": null, "pathId": null, "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "crm", "type": "sendWebhook", "parentId": "start", "pathId": null, "config": { "url": "https://crm.example.com/leads", "method": "POST", "headerNames": ["Authorization"] }, "position": { "x": 0, "y": 120 } } ] } ``` ## Errors | Status | When | | - | - | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP. | | `404` | No workflow has this id, or it is not in this key's scope. | # List workflows Source: https://docs.nedzo.ai/api-reference/workflows/list GET /workflows List the workflows in a workspace, newest first, a page at a time. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). List the workflows in a workspace, newest first. The list leaves out the steps. Get one workflow to see them. ## Query Parameters The workspace. Required for an account key. A workspace key uses its own workspace. How many to return, 1 to 100. How many to skip, up to 100,000. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workflows?limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "223e4567-e89b-12d3-a456-426614174000", "name": "Follow up after a call", "isPublished": true, "isActive": true, "version": 3, "publishedAt": "2026-10-11T09:00:00Z", "createdAt": "2026-10-01T09:00:00Z", "updatedAt": "2026-10-11T09:00:00Z" } ], "total": 1, "limit": 20, "offset": 0, "hasMore": false } ``` # Publish or unpublish a workflow Source: https://docs.nedzo.ai/api-reference/workflows/publish POST /workflows/{id}/publish Check a workflow and make its steps live, or stop it from starting new runs. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). ## Publish `POST /v1/workflows/{id}/publish` Check the workflow and make the current steps live. Each publish saves a new [version](/api-reference/workflows/versions). The workflow must be on to start runs. If the check finds problems, nothing is published and we answer `400` with every problem in `errors`. Each `field` names the step as `nodes..`. A problem with the whole workflow, like a missing trigger, uses `nodes`: ```json theme={null} { "type": "https://api.nedzo.ai/errors/validation-error", "title": "Validation Error", "status": 400, "detail": "The workflow has steps that are not ready. Fix the steps listed in errors.", "errors": [ { "field": "nodes.crm.config.url", "message": "Webhook URL is required" } ] } ``` If the workflow has a schedule trigger and we could not set up the schedule, the workflow is still published. The answer then has a `scheduleError` that says so. Publish again to try again. ## Unpublish `POST /v1/workflows/{id}/unpublish` Stop the workflow from starting new runs. Its steps stay as they are, and you can publish again later. ## Path Parameters The workflow's id. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000/publish" \ -H "Authorization: Bearer YOUR_API_KEY" ``` Both answer with the workflow, the same as [Get a workflow](/api-reference/workflows/get). ## Errors | Status | When | | - | - | | `400` | Publish: the workflow failed the check, or it has a step that turns DND off. `errors` lists each problem. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP, workflows are not on your plan, or the workspace is locked. | | `404` | No workflow has this id, or it is not in this key's scope. | # Replace a workflow Source: https://docs.nedzo.ai/api-reference/workflows/replace PUT /workflows/{id} Replace a workflow's name and steps, and the rules every step follows. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Replace the name and every step. Send the whole list of steps, not the ones that changed. On a published workflow, the new steps go live at once. New runs use them right away. So a PUT on a published workflow passes the same checks as [Publish](/api-reference/workflows/publish) first. If a step is not ready, we answer `400` and save nothing. The version number does not change until you publish again. A PUT on a published workflow cannot change its trigger. To change the trigger, [unpublish](/api-reference/workflows/publish) the workflow, send the PUT, then publish it again. If the workflow is published or unpublished while your PUT runs, we answer `409` and save nothing. Send it again. ## Path Parameters The workflow's id. ## Body The workflow's name, up to 200 characters. The trigger and the steps, up to 500. ## Steps Each item in `nodes` is the trigger or a step. | Field | What it is | | - | - | | `id` | Your id for the step. Each step needs its own id. Two steps with the same id get a `400`. | | `type` | The step type. See [Step types](/api-reference/workflows/step-types). | | `parentId` | The step before this one. The trigger has `null`. | | `pathId` | For a step after a condition: the branch it is on. | | `config` | The step's settings. See the `configSchema` in [Step types](/api-reference/workflows/step-types). | | `position` | Where the step sits in the dashboard editor. Leave it out and we use `{ "x": 0, "y": 0 }`. | Rules for steps: * **Webhook headers are write only.** A `sendWebhook` step takes `headers` as names and text values. We do not send the values back. Responses show `headerNames` in their place. * **Keep or change headers.** Leave `headers` out to keep the saved ones. That works while the `url` stays on the same address. To move the step to another address (a different host, http or https, or port), send `headers` again, or send `{}` to remove them. Otherwise we answer `400`. * **Header names and values.** A header name must be a plain name, and `Host`, `Content-Length`, `Transfer-Encoding` and `Connection` are not allowed. A value must be text on one line. * **Webhook addresses.** A new or changed `url` may not point to a private or internal address. We cannot check a `url` with a `{{variable}}` in it ahead of time. * **Secrets go in headers.** Only `headers` are write only. The `url` (with its query), `body` and `bodyFields` come back when you read the workflow or its versions. Put tokens and keys in `headers`. * **DND.** The API never saves or runs a step that turns DND off (`updateContact` with `"dnc": "deactivate"`). If you send one, or the workflow already has one from the dashboard, we answer `400`: "This workflow has a step that turns DND off. Remove that step in the dashboard first." A step can turn DND on. * **Lead Connector.** The step that changes a contact in Lead Connector is `updateLeadConnectorContact`. Each step that sends a message or makes a call checks DND for the contact when it runs. ```bash cURL theme={null} curl -X PUT "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Send new leads to my CRM", "nodes": [ { "id": "start", "type": "contactCreated", "parentId": null, "config": {} }, { "id": "crm", "type": "sendWebhook", "parentId": "start", "config": { "url": "https://crm.example.com/leads", "method": "POST", "headers": { "Authorization": "Bearer YOUR_CRM_TOKEN" } } } ] }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "223e4567-e89b-12d3-a456-426614174000", "name": "Send new leads to my CRM", "isPublished": true, "isActive": true, "version": 1, "publishedAt": "2026-10-11T09:00:00Z", "createdAt": "2026-10-10T09:00:00Z", "updatedAt": "2026-10-11T10:00:00Z", "nodes": [ { "id": "start", "type": "contactCreated", "parentId": null, "pathId": null, "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "crm", "type": "sendWebhook", "parentId": "start", "pathId": null, "config": { "url": "https://crm.example.com/leads", "method": "POST", "headerNames": ["Authorization"] }, "position": { "x": 0, "y": 0 } } ] } ``` ## Errors | Status | When | | - | - | | `400` | A field is missing or wrong, or a step breaks a rule above. On a published workflow, also when a step is not ready or the trigger changed. `errors` names the step as `nodes..`. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP. On a published workflow, also when the workspace is locked or its plan does not include workflows. | | `404` | No workflow has this id, or it is not in this key's scope. | | `409` | The workflow was published or unpublished while your PUT ran. Nothing was saved. Send it again. | # Runs Source: https://docs.nedzo.ai/api-reference/workflows/runs GET /workflows/{id}/runs List, get, cancel and retry the runs of a workflow. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). A run is one trip of one contact or event through a workflow. ## List runs * `GET /v1/workflows/{id}/runs`: runs of one workflow. * `GET /v1/workflows/runs`: runs of every workflow in a workspace. An account key must send `workspaceId`. Add `workflowId` to see one workflow. Both list newest first and take these query parameters: One of `running`, `waiting`, `completed`, `failed` or `cancelled`. `running` includes runs that have not started. How many to return, 1 to 100. How many to skip. ```json Response theme={null} { "items": [ { "id": "623e4567-e89b-12d3-a456-426614174000", "workflowId": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "223e4567-e89b-12d3-a456-426614174000", "status": "failed", "triggerType": "contact_created", "isTest": false, "errorMessage": "This step could not finish.", "startedAt": "2026-10-11T09:00:00Z", "completedAt": "2026-10-11T09:00:04Z" } ], "total": 1, "limit": 50, "offset": 0, "hasMore": false } ``` A run's `status` is `pending`, `running`, `waiting`, `completed`, `failed` or `cancelled`. A run's `triggerType` is what started it: `contact_created`, `contact_tagged`, `contact_tag_added`, `contact_tag_removed`, `call_completed`, `conversation_completed`, `conversation_started`, `contact_replied`, `webhook`, `schedule`, `pipedrive_person_updated`, or `manual` for a contact you enrolled. ## Get a run `GET /v1/workflows/{id}/runs/{runId}` One run with the result of each step it reached. ```json Response theme={null} { "id": "623e4567-e89b-12d3-a456-426614174000", "workflowId": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "223e4567-e89b-12d3-a456-426614174000", "workflowName": "Welcome text", "status": "completed", "triggerType": "contact_created", "isTest": false, "errorMessage": null, "contactId": "523e4567-e89b-12d3-a456-426614174000", "createdAt": "2026-10-11T09:00:00Z", "startedAt": "2026-10-11T09:00:00Z", "completedAt": "2026-10-11T09:00:02Z", "steps": [ { "stepId": "text", "name": "text", "type": "sendSms", "status": "completed", "startedAt": "2026-10-11T09:00:01Z", "completedAt": "2026-10-11T09:00:02Z", "errorMessage": null } ] } ``` A step's `name` is the name it has in the dashboard editor. A step with no name there shows its id. `errorMessage` is a plain line. Some errors have their own line, like "The contact has DND on." or "The contact has no phone number." Every other error reads "This step could not finish." We never send the raw error text. The step that failed has the same line as the run. ## Cancel a run `POST /v1/workflows/{id}/runs/{runId}/cancel` Stop a run that has not finished. It answers with the run's `id`, `workflowId`, `status` and `completedAt`. A run that has finished gets `409`. ## Retry a run `POST /v1/workflows/{id}/runs/{runId}/retry` Resume a failed run from the step that failed last. Steps that finished before it do not run again, and the run keeps its `id`. It answers `200` with the run's `id`, `workflowId` and `status`, which is `running`. The failed step runs again. So a step that may already have sent its message or call can't be retried. That is a step that timed out, failed after it started sending, or placed a call that got no answer back. A webhook your endpoint refused can be retried. That is a `4xx` error other than `408` or `429`, or a redirect we could not follow. It will be sent again. A webhook that got a `5xx`, `408` or `429` answer, or no answer in time, can't be retried. These runs get `409`: * A run that is not failed: cancelled, still going, already being retried, or completed. * A run with no failed step to resume from. * A run where steps ran after its failed step. * A run our system stopped because it stalled. * A run whose failed step may already have sent its message or call. * A run whose contact has restricted processing. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000/runs/623e4567-e89b-12d3-a456-426614174000/retry" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## Errors | Status | When | | - | - | | `400` | A query parameter is wrong, an account key sent no `workspaceId` to the workspace list, or a retry would run a step that turns DND off. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP. A retry needs workflows on your plan and a workspace that is not locked. | | `404` | No workflow or run has this id, or it is not in this key's scope. | | `409` | The run can't be cancelled or retried in its current state, its failed step may already have sent its message or call, or its contact has restricted processing. | | `502` | A retry could not check the workspace or plan status. Try again. | # Step types Source: https://docs.nedzo.ai/api-reference/workflows/step-types GET /workflows/action-types List the triggers that can start a workflow and the steps it can run. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Use these lists to build the `nodes` of a workflow. * `GET /v1/workflows/trigger-types`: the events that can start a workflow. The first step of a workflow is one of these. * `GET /v1/workflows/action-types`: the steps a workflow can run. Each item has a `type` to use in a step, a `name`, a `description` and `enabled`. A type with `"comingSoon": true` cannot be used yet. Some types have a `configSchema` that lists the settings the step takes. The API cannot give you a workflow's webhook address yet. To start a workflow with the `webhook` trigger, copy its address from the dashboard. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workflows/action-types" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "type": "sendWebhook", "name": "Webhook", "description": "Sends data to another system with an HTTP request.", "enabled": true, "configSchema": { "url": { "type": "string", "required": true }, "method": { "type": "string", "required": true, "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] }, "headers": { "type": "object", "required": false }, "body": { "type": "string", "required": false } } }, { "type": "updateLeadConnectorContact", "name": "Update Lead Connector contact", "description": "Changes the contact in Lead Connector.", "enabled": true } ] } ``` # Turn a workflow on or off Source: https://docs.nedzo.ai/api-reference/workflows/turn-on-off PATCH /workflows/{id} Turn a workflow on or off. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Turn a workflow on or off. A workflow that is off starts no new runs. Turning it on also needs a plan that includes workflows. ## Path Parameters The workflow's id. ## Body `true` to turn it on, `false` to turn it off. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "isActive": false }' ``` It answers with the workflow, the same as [Get a workflow](/api-reference/workflows/get). ## Errors | Status | When | | - | - | | `400` | `isActive` is missing or not true or false, or you turned on a workflow that has a step that turns DND off. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP, or you turned it on and workflows are not on your plan. | | `404` | No workflow has this id, or it is not in this key's scope. | # Versions Source: https://docs.nedzo.ai/api-reference/workflows/versions GET /workflows/{id}/versions List a workflow's published versions, and go back to an older one. These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). ## List versions `GET /v1/workflows/{id}/versions` Every published version, newest first. Each one has the steps as they were when it was published. Webhook header values are hidden, the same as on a workflow. How many to return, 1 to 100. How many to skip. ```json Response theme={null} { "items": [ { "id": "423e4567-e89b-12d3-a456-426614174000", "version": 3, "nodes": [], "publishedAt": "2026-10-11T09:00:00Z", "createdAt": "2026-10-11T09:00:00Z" } ], "total": 3, "limit": 50, "offset": 0, "hasMore": false } ``` ## Go back to a version `POST /v1/workflows/{id}/revert` Put back the steps of an older version. This saves a new version and unpublishes the workflow. Check it and [publish](/api-reference/workflows/publish) it again. The API cannot bring back a step that turns DND off. If the old version has such a step, we answer `400`: "This workflow has a step that turns DND off. Remove that step in the dashboard first." Pick another version. The version number to go back to. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workflows/123e4567-e89b-12d3-a456-426614174000/revert" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "version": 2 }' ``` It answers with the workflow, the same as [Get a workflow](/api-reference/workflows/get). ## Errors | Status | When | | - | - | | `400` | `version` is missing or below 1, or the version has a step that turns DND off. | | `402` | The account has no plan, or its subscription is not active. | | `403` | Your plan is not VIP. | | `404` | No workflow has this id, it is not in this key's scope, or the version does not exist. | # Attach payment method Source: https://docs.nedzo.ai/api-reference/workspace-billing/attach-payment-method POST /workspaces/{workspaceId}/billing/payment-methods/attach Attach a Stripe PaymentMethod to a workspace's billing customer via the Nedzo REST API. The first attached method automatically becomes the default. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID Stripe PaymentMethod ID (starts with `pm_`), already confirmed against the setup intent on the frontend. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/payment-methods/attach" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "paymentMethodId": "pm_1234567890abcdef" }' ``` ```json Response theme={null} { "paymentMethod": { "id": "pm_1234567890abcdef", "type": "card", "brand": "visa", "last4": "4242", "expMonth": 12, "expYear": 2030, "isDefault": true, "createdAt": "2026-04-17T10:30:00Z" }, "isDefault": true } ``` # Create setup intent Source: https://docs.nedzo.ai/api-reference/workspace-billing/create-setup-intent POST /workspaces/{workspaceId}/billing/payment-methods/setup-intent Create a Stripe SetupIntent for attaching a new payment method to a workspace. Returns a client_secret for collecting card details with Stripe.js. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/payment-methods/setup-intent" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "clientSecret": "seti_1234567890_secret_abc123", "customerId": "cus_1234567890abcdef" } ``` # Credit wallet Source: https://docs.nedzo.ai/api-reference/workspace-billing/credit-wallet POST /workspaces/{workspaceId}/billing/wallet/credit Add credits to a workspace wallet as a manual adjustment via the Nedzo REST API. Uses an atomic operation and records the change in the ledger. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID Amount to credit in cents. Must be a positive integer. Human-readable reason for the adjustment (1-500 characters). Cannot contain the substring `[idempotency:`. Optional idempotency key (a-z, A-Z, 0-9, hyphen, 1-128 chars). If provided, a retry with the same key within 24 hours returns the existing transaction without re-applying the credit. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/wallet/credit" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 2500, "reason": "Customer goodwill credit", "idempotencyKey": "credit-2026-04-17-abc" }' ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "transactionId": "c4e5b6d7-1234-5678-9abc-def012345678", "amount": 2500, "newBalance": 7500, "type": "credit", "reason": "Customer goodwill credit", "createdAt": "2026-04-17T10:30:00Z" } ``` # Debit wallet Source: https://docs.nedzo.ai/api-reference/workspace-billing/debit-wallet POST /workspaces/{workspaceId}/billing/wallet/debit Deduct funds from a workspace wallet as a manual adjustment via the Nedzo REST API. Returns 402 if the balance is insufficient for the debit. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID Amount to debit in cents. Must be a positive integer. Human-readable reason for the adjustment (1-500 characters). Cannot contain the substring `[idempotency:`. Optional idempotency key (a-z, A-Z, 0-9, hyphen, 1-128 chars). If provided, a retry with the same key within 24 hours returns the existing transaction without re-applying the debit. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/wallet/debit" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "reason": "Chargeback correction", "idempotencyKey": "debit-2026-04-17-xyz" }' ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "transactionId": "d5f6a7b8-2345-6789-abcd-ef0123456789", "amount": 1000, "newBalance": 6500, "type": "debit", "reason": "Chargeback correction", "createdAt": "2026-04-17T10:35:00Z" } ``` ```json 402 Insufficient Balance theme={null} { "type": "https://api.nedzo.ai/errors/payment-required", "title": "Payment Required", "status": 402, "detail": "Insufficient wallet balance" } ``` # Get wallet balance Source: https://docs.nedzo.ai/api-reference/workspace-billing/get-balance GET /workspaces/{workspaceId}/billing/balance Retrieve the current wallet balance for a workspace in cents via the Nedzo REST API. Use this endpoint to check available funds before debiting. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/balance" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "balance": 5000, "currency": "usd" } ``` # Get usage summary Source: https://docs.nedzo.ai/api-reference/workspace-billing/get-usage GET /workspaces/{workspaceId}/billing/usage Aggregates wallet transactions across a date range and returns total spend, total top-ups, total refunds, net adjustments, and a per-type breakdown. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID Inclusive ISO 8601 lower bound on `created_at`. Exclusive ISO 8601 upper bound on `created_at`. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/usage?startDate=2026-04-01T00:00:00Z&endDate=2026-05-01T00:00:00Z" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "startDate": "2026-04-01T00:00:00Z", "endDate": "2026-05-01T00:00:00Z", "currency": "usd", "totalSpendCents": 8500, "totalTopupCents": 20000, "totalRefundCents": 0, "totalAdjustmentCents": 1500, "byType": { "usage": -8500, "topup": 20000, "refund": 0, "adjustment": 1500 } } ``` # List payment methods Source: https://docs.nedzo.ai/api-reference/workspace-billing/list-payment-methods GET /workspaces/{workspaceId}/billing/payment-methods List all Stripe payment methods attached to a workspace via the Nedzo REST API. Returns an empty array if no Stripe customer exists yet. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/payment-methods" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} [ { "id": "pm_1234567890abcdef", "type": "card", "brand": "visa", "last4": "4242", "expMonth": 12, "expYear": 2030, "isDefault": true, "createdAt": "2026-04-15T10:30:00Z" } ] ``` # List wallet transactions Source: https://docs.nedzo.ai/api-reference/workspace-billing/list-transactions GET /workspaces/{workspaceId}/billing/transactions Retrieve a paginated list of wallet transactions for a workspace. Filter by date range and transaction type for billing reconciliation. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID Max transactions to return per page (1-100). Offset for pagination. Inclusive ISO 8601 lower bound on `created_at`. Exclusive ISO 8601 upper bound on `created_at`. Filter by transaction type: `topup`, `usage`, `refund`, or `adjustment`. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/transactions?limit=20&offset=0&type=adjustment" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "id": "c4e5b6d7-1234-5678-9abc-def012345678", "type": "adjustment", "amount": 2500, "balanceAfter": 7500, "description": "Manual credit: Customer goodwill credit", "createdAt": "2026-04-17T10:30:00Z" }, { "id": "d5f6a7b8-2345-6789-abcd-ef0123456789", "type": "adjustment", "amount": -1000, "balanceAfter": 6500, "description": "Manual debit: Chargeback correction", "createdAt": "2026-04-17T10:35:00Z" } ], "total": 2, "limit": 20, "offset": 0, "hasMore": false } ``` # Create workspace Source: https://docs.nedzo.ai/api-reference/workspaces/create POST /workspaces Create a new workspace in Nedzo via the REST API. Provide a name and optional settings to set up an isolated environment for agents and contacts. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace name (1-255 characters) Workspace description (max 1000 characters) IANA timezone identifier (e.g., "America/Los\_Angeles", "Europe/London") Workspace icon URL Primary contact name Primary contact phone number Primary contact email address Street address State or province ZIP or postal code Country code or name Business registration number (EIN, VAT, etc.) ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789", "balance": 0, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Delete workspace Source: https://docs.nedzo.ai/api-reference/workspaces/delete DELETE /workspaces/{id} Permanently delete a workspace and all associated agents, contacts, and call data via the Nedzo REST API. This action cannot be undone. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/workspaces/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} // 204 No Content ``` # Get workspace Source: https://docs.nedzo.ai/api-reference/workspaces/get GET /workspaces/{id} Retrieve a single workspace by its UUID using the Nedzo REST API. Returns the workspace name, creation date, phone number, and configuration details. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/workspaces/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "My Workspace", "description": "Main workspace", "timezone": "America/New_York", "icon": null, "contactName": null, "contactEmail": null, "contactPhone": null, "streetAddress": null, "state": null, "zip": null, "country": null, "businessRegistrationNumber": null, "balance": 0, "isRebilled": false, "rebillingMarkup": 1, "autoTopupEnabled": false, "autoTopupThreshold": null, "autoTopupAmount": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # List workspaces Source: https://docs.nedzo.ai/api-reference/workspaces/list GET /workspaces Retrieve all workspaces accessible to your API key via the Nedzo REST API. Returns workspace IDs, names, and metadata for scoped keys. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/workspaces" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "My Workspace", "description": "Main workspace", "timezone": "America/New_York", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] ``` # Update workspace Source: https://docs.nedzo.ai/api-reference/workspaces/update PATCH /workspaces/{id} Update an existing workspace by ID via the Nedzo REST API. Change the workspace name, settings, or configuration with a PATCH request to the endpoint. This endpoint is for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access). Workspace UUID Workspace name (1-255 characters) Workspace description (max 1000 characters) IANA timezone identifier (e.g., "America/Los\_Angeles", "Europe/London") Workspace icon URL Primary contact name Primary contact phone number Primary contact email address Street address State or province ZIP or postal code Country code or name Business registration number (EIN, VAT, etc.) Enable or disable rebilling on this workspace. When true, the workspace is charged directly via its own Stripe Connect customer. Enabling requires Stripe Connect to be set up on the parent account. Multiplier applied to wallet top-up charges when rebilling is enabled (1 = no markup, 10 = 10x markup). Range 1-10. Enable or disable auto-topup. Enabling requires both `autoTopupThreshold` and `autoTopupAmount` to be set. Balance threshold in cents; when the wallet falls below this, auto-topup triggers. Pass `null` to clear. Amount in cents to charge when auto-topup triggers. Pass `null` to clear. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789" }' ``` ```json Response theme={null} { "id": "19c3b12f-ec54-43ad-8686-00e921f1befd", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789", "balance": 0, "isRebilled": false, "rebillingMarkup": 1, "autoTopupEnabled": false, "autoTopupThreshold": null, "autoTopupAmount": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T11:00:00Z" } ``` # Account alerts Source: https://docs.nedzo.ai/billing/account-alerts The full-width alert strip at the top of the app. What raises it, where each button sends you, and which alerts stay dismissed. When something breaks at the account level, Nedzo shows a full-width strip across the top of the app. The strip pushes the page down instead of floating over it, so it cannot be missed or scrolled past. Alerts are read live. Fix the cause and the strip clears itself on the next page view. ## What raises an alert Red alerts mean traffic is stopping right now. Amber alerts mean it will stop soon unless you act. | Alert | Severity | Button | Where it sends you | | - | - | - | - | | Your last payment failed | Red | Update payment method | Payment methods | | Out of balance | Red | Top up | [Wallet](/billing/managing-your-subscription#wallet) | | *Provider* is disconnected | Red | Reconnect | Integrations, with that provider's panel already open | | The 10DLC campaign for *business* was deactivated | Red | Fix registration | Your A2P registration | | *Number* is suspended | Red | Fix billing | That phone number | | Wallet running low | Amber | Top up | [Wallet](/billing/managing-your-subscription#wallet) | | Plan allowance nearly used | Amber | Open wallet | [Wallet](/billing/managing-your-subscription#wallet) | Both wallet alerts land on the **Wallet** tab, not Payment methods. Wallet is where your funds, the auto top-up toggle and the top-up threshold live. If you have no card on file, Wallet tells you so and links you to add one. ## Stacking At most two alerts show at once. Nedzo sorts by severity first, so you always see the worst two. The rest appear as you clear the ones above them. ## Dismissing an alert Every alert has a close button. Most come back on the next page load, because the problem is still there and it stops your traffic. Two stay closed until your allowance period rolls over: * Wallet running low * Plan allowance nearly used These are warnings about something that has not happened yet, so Nedzo remembers that you closed them rather than asking again on every page. The dismissal expires with the period, so a new period raises the warning again if the balance is still low. # Managing Your Subscription Source: https://docs.nedzo.ai/billing/managing-your-subscription Subscribe, upgrade, downgrade, cancel, and handle failed payments for your Nedzo plan. Everything on the Billing page, tab by tab. Your subscription lives in **Settings > Subscription**, split into a **Billing** page and a **Usage** page. This page walks through the Billing page tab by tab: **Plan**, **Wallet**, **Payment methods**, and **Invoices**. Only workspace **owners and admins** can start, change, or cancel a subscription. Other roles see the same tabs read-only. See [Plans & Pricing](/billing/plans) for what each plan includes. ## Starting a subscription If your account doesn't have an active plan yet, the **Plan** tab shows the plan picker: Business, Agency, and Pro side by side, with what's included in each. Switch between **Monthly** and **Yearly** at the top. Yearly gives you two months free. 1. Click **Get Started** on the plan you want. 2. Nedzo opens Stripe's embedded checkout right in the page — enter your card and confirm. 3. When it's done, you're back on the Plan tab and your plan is active right away. Subscribing through the Billing page charges your card right away. There's no free trial on this path. VIP isn't bought here. Choose **Talk to us** on VIP and we set it up with you. See [VIP](/billing/plans#vip). ## Signing up and your free trial A new account starts with a 14-day free trial. Each customer gets one trial. * **Password.** Your password needs at least 8 characters, with an uppercase letter, a lowercase letter and a number. A symbol is not needed. A checklist under the field ticks off each rule as you type, and you can't continue until all of them pass. * **Email already has an account.** Signup says *You already have an account with this email.* and shows a **Log in** link with your email filled in. No new account is made. * **Card or phone number already used for a trial.** Signup says *This card has already been used for a free trial.* or *This phone number has already been used for a free trial.* Click **Start your plan** to pay today and start your plan with no trial, or log in to your existing account. * **Coming back after your plan ended.** Log in as the account owner. Nedzo takes you to pick a plan and pay, with no second trial. Once you pay, you go straight into the app. Other members of the account log in as before. ## Buying phone numbers on a trial Buying an additional phone number requires a paid plan. If you're on a trial and open the Buy Number flow, you'll be prompted to upgrade first rather than shown search results. Your free signup number keeps working throughout the trial — this only applies to buying extra numbers. Once you're on a paid plan, purchasing is available immediately. See [phone number rental](/billing/plans#phone-number-rental) for what it costs. ## Changing plans Once you have an active subscription, the Plan tab shows your current plan with **Change plan** and **Cancel** actions. You can move between Business, Agency, and Pro yourself. VIP changes go through your Nedzo contact. **Change plan** opens the full pricing table with all four plans in one row. If the row is wider than your screen, scroll it sideways. Your current plan is marked and can't be picked. Prices follow your current billing period, monthly or yearly, since a plan change keeps it. There is no Monthly / Yearly switch here. Pick a plan to see a summary of the change before you confirm it: | Line | What it shows | | - | - | | Plan | Your current plan and the new one | | Price | The new plan's price | | Starts | **Today** for an upgrade, or the end of your current period for a downgrade | | Billing | What you are charged and when | Below that, **What you get** lists what the new plan includes. On a yearly plan, an upgrade warns you that a new year is charged now. **Upgrading** (for example Business to Agency) applies immediately: * Your billing date moves to today, and you're charged for a new period now. * The unused time on your current plan is not credited back. * If you're still within a trial, there's no charge. Your trial allowance stays as it is until the trial ends, and you're billed the new plan when it does. **Downgrading** (for example Agency to Business) takes effect at the end of your current billing period: * No charge now, and you keep your current plan and allowance until then. * Your plan switches automatically at the next billing date. ## Add-ons Add-ons sit on the **Plan** tab, under your plan card. There is no separate Add-ons tab. See [Add-ons](/billing/plans#add-ons) for what each one costs. ### Extra seats The **Team seats** card on the Plan tab shows how many seats you use, for example *4 of 5 included*, and how many extra seats you bought. Owners and admins buy and change extra seats there. The card has no button on VIP, which has unlimited seats. 1. Click **Add seats**. Once you have extra seats, the button reads **Change seats**. 2. Set the number of extra seats with **-** and **+**. The popup shows the price, \$29/seat/mo. 3. Read the note under the price. It says what you are charged. 4. Click **Confirm**. **Adding seats** is charged right away: | Your plan | What you pay now | | - | - | | Monthly plan | The added seats, prorated for the rest of this billing period. From then on they show as their own line on your plan invoice. | | On a trial | Nothing. The seats are yours now and billed with your plan when the trial ends. | | Yearly plan | Your first seats start a separate monthly charge, billed in full now: *You're charged \$58.00 now for 2 seats, then monthly.* Seats you add after that are prorated. | **Removing seats** takes effect at the start of your next billing period. You keep all the seats you paid for until then, with no refund for the current period. The card shows the drop that is coming. Set the number to 0 to end your extra seats. You can make one seat change at a time. ## Canceling and reactivating **Cancel** ends your subscription at the end of the current billing period. You keep access until then, with no further charges after. While your plan is set to cancel, the Plan tab shows *Cancels on* and the date, and one button: **Don't cancel**. Click it any time before the end date to take the cancellation back and stay on your current plan. It works for a cancellation at the end of the period and for one set to a fixed date. Once a subscription actually ends (not just scheduled to cancel), Ned features are no longer available for the workspace. ### What happens to your data after you cancel Nedzo keeps a cancelled account's data for **30 days** after the subscription ends. Reactivate within those 30 days and everything is still there: contacts, conversations, agents, workflows, numbers, and integrations. After 30 days, Nedzo deletes the account's data for good. This covers every workspace in the account: * Conversations, messages, call recordings, and transcripts * Contacts, with their tags, custom field values, notes, and contact memory * Agents, workflows and workflow runs, and knowledge base files * Connected integrations, email addresses, WhatsApp senders, and API keys * Phone numbers, which are released This cannot be undone. To keep your data, reactivate before the 30 days are up. Accounts on a custom contract are not deleted this way. Talk to your Nedzo contact about those. ## Payment failures If a scheduled charge fails (expired card, insufficient funds), Nedzo doesn't cut you off immediately: 1. **First failure** — Stripe retries automatically over the following days. Ned keeps working, and the account owner gets an email each time a retry fails. 2. **After the retry schedule is exhausted** (about 14 days) — the subscription is marked unpaid and Ned features are frozen for the workspace until payment succeeds. 3. **Update your card and pay the open invoice** (from the **Payment methods** tab, or your Stripe payment email). Ned access comes back as soon as the payment goes through. ### Paid add-ons pause right away Your **plan** follows the retry schedule above. Paid **add-ons** do not. White-label, CPS capacity, extra seats, and HIPAA compliance all switch off as soon as the first renewal charge fails, not after the 14-day retry window. See [Add-ons](/billing/plans#add-ons) for the full list and what each one costs. | | Plan features | Paid add-ons | | - | - | - | | First failed charge | Keep working | Switch off | | During Stripe's retries | Keep working | Stay off | | Retries exhausted (\~14 days) | Frozen | Stay off | | Payment succeeds | Restored | Restored | If white-label is paused, your branding stops applying and the app falls back to Nedzo's. CPS capacity drops back to your plan's normal limit. **HIPAA compliance** switches off too. The controls turn off during dunning, so recordings, transcripts, and logs are stored again until the payment goes through. **Extra seats** drop to 0. Members you already have keep their access and nobody gets removed. You just cannot activate a new member until the charge succeeds. Everything comes back on its own the moment the payment goes through. You keep the same add-on, at the same quantity, and there is nothing to re-purchase. Pay the open invoice from the **Payment methods** tab and the add-on turns back on. ## Wallet The **Wallet** tab in **Settings > Subscription > Billing** holds your prepaid balance. Calls, messages, and phone numbers draw down from it. On plans that bill usage on the invoice instead, the tab says the balance is not used for billing yet. Only account **owners and admins** can add funds or change auto top-up. Other roles see the balance read-only. ### Add funds 1. Click **Add funds**. It's the main button on the balance card. 2. Enter an **Amount to add**. It starts at $25.00. The minimum is $10 and the maximum is \$1,000. 3. Click **Add \$X**. Nedzo charges the card on file. When the payment goes through you see *"Payment received. Your balance is up to date."* If the balance takes a moment to catch up, the message says so and tells you there is no need to pay again. ### One payment at a time Each top-up is one attempt, and Nedzo never charges the same attempt twice. This holds if you switch tabs, reload the page, or open Billing in a second tab while a payment is running. * **While a payment runs**, every Billing tab shows *"A payment of \$X is in progress. This page updates when it finishes."* **Add funds** stays off until it is confirmed. * **Starting a second payment in another tab** is blocked with *"A payment is already running in another tab. Wait for it to finish."* * **If Nedzo can't tell whether a payment went through**, for example after a lost connection, you see *"Your payment of \$X may have gone through. Check it before you pay again."* Click **Check payment**. It checks the same attempt again. If it was paid, you see the success message and are not charged a second time. * **If your bank declines**, you see the bank's reason and **Add funds** is available again right away. If you are unsure whether a top-up went through, look at **Wallet activity** before you pay again. ### Auto top-up Turn on **Auto top-up** to add funds when your balance runs low. It sits inside the balance card, under your balance. | Setting | Range | What it does | | - | - | - | | Top up when my balance drops below | $5 – $500 | The balance that triggers a top-up | | Then add | $25, $50, $100, $250, $500, or Custom ($10 – \$1,000) | How much each top-up adds | Changes save as you make them. There is no Save button. ### Wallet activity **Wallet activity** lists every change to your balance, newest first. Each row shows: * What it was: the description, or a type such as **Top-up**, **Auto top-up**, **Usage**, **Refund**, or **Adjustment** * When it happened * The amount, with credits in green (for example `+$25`) and charges as `-$1.20` * Your balance right after it The list shows 20 rows at a time. Click **Load more** to see older ones. It refreshes on its own when a top-up settles. ## Payment methods The **Payment methods** tab shows the card on file and your billing details (company name, billing address). Invoices use these details. To add or replace a card: 1. Click **Manage payment methods**. Only owners and admins see this button. 2. Stripe's secure page opens. Add your new card and make it the default. 3. Stripe sends you back to Nedzo. Nedzo saves the new default card to your account and moves your live subscriptions onto it. If the card doesn't save, you see *"We could not save your new card. Refresh the page to try again."* ## Invoices The **Invoices** tab lists every past invoice, downloadable, with amount and status. Rented phone numbers appear as one line per number, so a four-number invoice has four separate lines. ## Emails when included usage runs out The account owner and admins get an email when the account uses 50% and 80% of its included call minutes or messages, and again when they are all used. The "used up" email says what happens next, based on your wallet: | Your wallet | What the email says | | - | - | | Has money and auto top-up can refill it | Nothing has stopped. Calls and messages now draw on the wallet. | | Has money, but nothing refills it | Calls and messages run on the wallet balance and stop when it runs out. It tells you how to keep them going. | | Is empty and nothing refills it | The subject ends with *and calls have stopped* or *and messages have stopped*. The email says what fixes it: top up, turn on auto top-up, or reactivate your plan. | | Can't be read | Calls and messages draw on the wallet, and stop if it runs out and nothing refills it. | The button opens your usage and wallet. If your plan is cancelled or not active, it opens the plan picker so you can reactivate. Your included minutes and messages come back when your billing period renews. When a single workspace stops because of its own limits or an empty wallet, you get a separate stop email for it. See [Workspace limits](/billing/workspace-limits#the-stop-email). ## Usage The Billing page no longer shows a projected total for the current period. To see what you've used this period, open the **Usage** page in **Settings > Subscription**. Your prepaid balance shows there as **Wallet balance**. # Plans & Pricing Source: https://docs.nedzo.ai/billing/plans Nedzo's plans: Business, Agency, Pro, and VIP. What's included, and how telephony and email usage are charged. Nedzo bills on a simple model: a flat monthly plan, plus usage-based charges for the channels you actually use. Plans are managed from **Settings > Subscription > Billing**. See [Managing your subscription](/billing/managing-your-subscription) for how to subscribe, change plans, and handle payment issues. ## Plans | | **Business** | **Agency** | **Pro** | **VIP** | | - | - | - | - | - | | Price | \$99/month | \$299/month | \$499/month | Talk to us | | Call minutes included | 400 | 1,200 | 2,500 | Agreed with you | | Messages included | 500 | 2,000 | 5,000 | Agreed with you | | Seats included | 1 | 5 | 10 | Unlimited | | Workspaces | 1 | 10 | 20 | Unlimited | | Concurrent calls | 5 | 10 | 25 | 50 | | Knowledge sources | 10 | Unlimited | Unlimited | Unlimited | | Assistant questions included | 50 | 250 | Unlimited | Unlimited | | Channels | Voice, SMS, email | Voice, SMS, email, web chat | All, including WhatsApp, Instagram and Messenger | All | | Workflows | No | Yes | Yes | Yes | | API access | No | No | No | Yes | | Priority support | No | No | Yes | Yes | | White-label | Add-on | Add-on | Add-on | Included | Pay yearly and you get two months free. Usage past your plan's included minutes and messages bills at the overage rate: | | **Business** | **Agency** | **Pro** | **VIP** | | - | - | - | - | - | | Per extra call minute | \$0.25 | \$0.25 | \$0.20 | \$0.16 | | Per extra message | \$0.01 | \$0.01 | \$0.01 | \$0.01 | Business, Agency, and Pro are bought from **Settings > Subscription > Billing**. VIP is set up with you, not bought online. We agree your minutes, messages, and white-label setup, then turn it on. See [VIP](#vip) below. ## Seats A seat is one member of your workspace. Every plan comes with a set number: | Plan | Seats included | | - | - | | **Business** | 1 | | **Agency** | 5 | | **Pro** | 10 | | **VIP** | Unlimited | Extra seats cost **\$29 per seat, per month**. You buy them yourself with **Add seats** on the **Team seats** card, on the **Plan** tab of **Settings > Subscription > Billing**. No sales call, no waiting. On a monthly plan, extra seats show as their own line on your plan invoice. See [Extra seats](/billing/managing-your-subscription#extra-seats) for how adding and removing seats is charged. Extra seats are sold on **Business**, **Agency**, and **Pro** only. VIP already has unlimited seats. ## Usage-based charges On top of the monthly plan fee, these are billed as you use them and itemized on your invoice each period: | Item | Billed as | | - | - | | Call minutes (voice, inbound + outbound) | Per minute | | SMS | Per message | | WhatsApp messages | Per message | | Phone number rental | Flat \$0.99 per number, per month | | Web call minutes (browser-based voice) | Per minute | | Outbound email sends | Per email | | A2P 10DLC registration | Passed through at carrier cost | Call minutes, SMS, WhatsApp, and email rates are usage-based (cost plus a small margin, with a minimum floor per unit). Phone number rental is a flat price, not cost-derived. All of them appear as separate line items on your invoice. ### Phone number rental The flat \$0.99 applies to local US and Canada numbers, which are the numbers available to buy in the dashboard. Toll-free and premium/non-geographic numbers aren't offered for purchase. Two things to know about how it's charged: * **The first month is prorated.** You pay for the days remaining in the current billing period at purchase, not a full month. The full \$0.99 is charged from the next renewal onward. * **Each number is its own invoice line.** If you rent four numbers you'll see four lines, each labeled with the number, rather than one bundled "phone numbers" charge. Makes it straightforward to see what you're paying for and to reconcile after adding or removing a number mid-period. Every new account also gets one free US number assigned automatically at signup — it carries no rental charge. ## Add-ons Add-ons are flat monthly charges on top of your plan fee. Turn them on from **Settings > Subscription > Billing**, on the **Plan** tab under your plan. | Add-on | Price | What you get | | - | - | - | | **White-label** | \$500/month | Your branding across the app in place of Nedzo's. Included at no charge on VIP. | | **CPS capacity** | \$25 per unit, per month | Each unit raises how many calls per second your workspace can start. | | **Extra seats** | \$29 per seat, per month | One more workspace member. See [Seats](#seats) for which plans can buy them. | | **HIPAA compliance** | \$299/month | Stops Nedzo storing call recordings, transcripts, and logs. Needs a signed BAA first. | ### White-label branding With white-label on, your custom domain shows your brand and never Nedzo's. * **Logo.** Your uploaded logo shows across the app. If you haven't uploaded one, a grey tile with the first letter of your brand name shows in its place. With no brand name, the tile is blank. * **Browser tab.** The tab shows your brand name, or your domain name if no brand name is set. The tab icon is your uploaded favicon, or blank if you haven't uploaded one. The Nedzo icon never shows on your domain, not even while the page loads. * **Unibox.** AI messages show your workspace icon or logo. If neither is set, they show the first letter of the agent's name, or of your brand name. HIPAA compliance works on every plan. You need a signed BAA before you can buy it, so [contact support](mailto:support@nedzo.ai) to get one in place. The in-app toggle stays disabled until the add-on is active. See [HIPAA compliance](/agents/voice-agents#hipaa-compliance). Add-ons stop the moment a renewal charge fails, even though your plan keeps working. See [Paid add-ons pause right away](/billing/managing-your-subscription#paid-add-ons-pause-right-away). ## VIP VIP is set up with you rather than bought online. We agree your minutes, messages, and white-label setup, then provision it. White-label is included at no extra charge, and VIP is the plan with API access and SIP trunking. To talk about VIP, choose **Talk to us** on the VIP plan in **Settings > Subscription > Billing**, or [contact support](mailto:support@nedzo.ai). # Workspace limits Source: https://docs.nedzo.ai/billing/workspace-limits Set monthly limits per workspace on included minutes, included messages and wallet spend, so one workspace can't use up the whole account. Every workspace on an account shares the same plan allowance and the same wallet. One busy workspace can use up minutes and money meant for the others. **Monthly limits** stop that. Each workspace can have three optional limits: | Limit | Unit | What happens when the workspace reaches it | | - | - | - | | **Minutes** | Included call minutes | The workspace stops using the account's included minutes. Its calls are then paid from the wallet, at your plan's [overage rate](/billing/plans#plans). | | **Messages** | Included messages | The workspace stops using the account's included messages. Its messages are then paid from the wallet, at your plan's overage rate. | | **Wallet spend** | Dollars | The workspace can't spend more from the wallet. New calls and messages in it stop. | All three reset when your account's billing period renews, together with your included minutes and messages. Agencies use these to give each client workspace its own share. For example, give a client 300 minutes and a $50 wallet budget a month. Once they use the 300 minutes, their calls draw on the wallet. Once they spend $50 from it, their calls stop until the next period. ## Who can set them Only the **account owner** can set or change limits. Admins can see the limits and what each workspace used, but can't change them. The stop email tells admins to ask the owner. ## Set a limit 1. Open **Settings > Admin > Workspaces**. 2. Open the workspace. 3. Find the **Monthly limits** card. 4. Type a number in **Minutes**, **Messages** or **Wallet spend**. 5. Click **Save**. How the boxes work: * **Empty means no limit.** This is the default. Clear a box to remove its limit. * **0 is a real limit.** A minute limit of 0 means the workspace never uses included minutes, so every call is paid from the wallet. A spend limit of \$0 means the workspace can't spend from the wallet at all. * **Minutes and Messages** take whole numbers, for example `300`. * **Wallet spend** takes dollars, for example `200` or `200.50`. * Only the limits you changed are saved. The others keep their value. * If Nedzo can't save a limit, the card shows why. For example, a minute limit needs a plan that has a rate for extra minutes. ## What the card shows Under each limit, the card shows what the workspace used this period: * *120 of 300 minutes used this period* * *450 of 500 messages used this period* * *$32.10 of $50.00 spent this period* It also warns you when a limit is reached: | Message | What it means | | - | - | | Minute limit reached. Calls are now paid from the wallet. | The workspace used its included minutes. Calls keep going on the wallet. | | Less than 5 minutes left, counting calls in progress. New calls also need money in the wallet. | Fewer than 5 included minutes are left, after counting calls that are still running. A new call uses them first and the wallet pays the rest, so it needs wallet money too. | | Message limit reached. Messages are now paid from the wallet. | The workspace used its included messages. Messages keep going on the wallet. | | This workspace has reached its limit and has stopped spending from the balance. | The wallet spend limit is reached. New calls and messages in this workspace stop. | If the card can't load the limits, the boxes say **Unavailable** instead of **No limit**. ## When a workspace stops A workspace stops taking new calls and messages when it can't pay for them. This happens when: * it reached its **wallet spend** limit, or * its included minutes or messages are used (its own limit, or the account's whole allowance), and the wallet is empty with nothing to refill it. Calls already running finish. They are only cut off when the account runs out of both included minutes and wallet balance. If Nedzo can't read a workspace's limits, it does not let that workspace start new paid calls or messages until it can. A limit is never skipped by mistake. ### The stop email When a workspace stops, the account owner and admins get an email. The subject says what stopped, for example *Calls stopped in Acme Dental* or *Messages stopped in Acme Dental*. The email says why it stopped and what fixes it: | Why | What fixes it | Button | | - | - | - | | The workspace reached its monthly spending limit | The account owner raises the workspace's limits | View workspace limits | | The included minutes or messages are used and the wallet is empty | Top up the wallet or turn on auto top-up | Top up wallet | | The wallet is empty and auto top-up didn't cover it | Top up, or check your card and auto top-up settings | Top up wallet | | The plan is cancelled or not active | Reactivate your plan | Reactivate plan | Each workspace sends at most one stop email a day, so a busy workspace doesn't flood your inbox. ## Related * [Wallet and auto top-up](/billing/managing-your-subscription#wallet) * [Emails when included usage runs out](/billing/managing-your-subscription#emails-when-included-usage-runs-out) * [Campaigns: Out of funds](/campaigns/overview#out-of-funds) # Campaigns Source: https://docs.nedzo.ai/campaigns/overview Run outbound calling campaigns from a saved contact view. Set the agent, daily minute cap, follow-ups, calling days and hours, and timezone, then track every contact the campaign reaches. A campaign calls everyone in a saved contact view with one of your voice agents. The view decides who gets called. The campaign decides when, how often, and how much it can spend each day. Open **Campaigns** from the sidebar, between Contacts and Workflows. Any member of the workspace can use it. Outbound calling campaigns are not the same as A2P 10DLC campaigns. A 10DLC campaign is your SMS carrier registration. See [A2P 10DLC registration](/telephony/a2p-10dlc-registration). ## Before you start You need two things: * **A voice agent** to place the calls. Any voice agent can be picked. See [Voice agents](/agents/voice-agents). * **A saved view** on the Contacts page. The view's filter decides who is enrolled. See [Saved views](/concepts/contacts). If either list is empty, the picker in the campaign panel says **No voice agents yet** or **No saved views yet**. ## Create a campaign 1. On the Campaigns page, click **New campaign**. 2. Fill in the fields below. 3. Click **Create campaign**. New campaigns start as **Draft**, so creating one never starts calls. Open a draft to see who it will call. Switch it on from the list when you are ready. ### Fields | Field | Range | Default | What it does | | - | - | - | - | | Name | Up to 200 characters | — | How the campaign shows in the list. Required. | | Agent | Any voice agent | — | The agent that places the calls. Required. | | Daily usage cap | 1–10,000 minutes | 60 | Connected talk minutes the campaign may use per day. | | View | Any saved view | — | Everyone the view matches gets enrolled. Required. | | Maximum follow-ups | 0–20 | 2 | Attempts after the first. `0` means call once and stop. | | Calling days | Mon–Sun | Mon–Fri | Days the campaign may call. Pick at least one. | | Timezone | Contact's timezone, or Specific timezone | Contact's timezone | Whose clock the calling days and hours follow. See [Which timezone is used](#which-timezone-is-used). | | Campaign timezone | Any city timezone | Your browser's timezone | Shows only when Timezone is **Specific timezone**. Required then. | | Local calling hours | 9:00 AM – 8:00 PM, in 15-minute steps | 9:00 AM – 5:00 PM | The window calls go out in, in the timezone above. | To change a campaign later, open the **…** menu on its row and pick **Edit**. The same panel opens with a **Save changes** button. ## Calling days and hours Campaigns only call between **9:00 AM and 8:00 PM**, in the contact's local time or in the campaign's timezone if you set one. You cannot pick a window outside those hours. * **15-minute steps.** Start times run from 9:00 AM to 7:45 PM. End times run from 9:15 AM to 8:00 PM. * **Exact edges.** The window is used to the minute. A 9:30 AM – 5:45 PM window starts calling at 9:30 AM and places its last call before 5:45 PM. * **The end is not included.** An 8:00 PM end means the last call starts before 8:00 PM. * **The end must come after the start.** Otherwise you see *"The window has to end after it starts"*. * **Calling days follow the contact's day too.** A Mon–Fri campaign can still call a contact in Los Angeles on Friday evening, even after your workspace clock has moved to Saturday. ### Campaigns saved with older hours Campaigns created before this rule may have hours outside 9:00 AM – 8:00 PM. * **When you edit one**, the panel shows the saved hours as they are and flags them: *"Calls can only be scheduled between 9:00 AM and 8:00 PM"*. You cannot save until you pick a valid window. * **If you never edit it**, the campaign still only dials inside 9:00 AM – 8:00 PM. A saved 8:00 AM – 9:00 PM window dials from 9:00 AM to 8:00 PM. A window that sits fully outside those hours dials nobody. ### Which timezone is used The **Timezone** field picks whose clock the campaign follows. **Contact's timezone** (the default). Each contact is called in their own timezone, not yours. 1. If the contact has a valid timezone, Nedzo uses it. 2. If the contact has no timezone, Nedzo uses your workspace timezone, but only if you have set one. The default UTC does not count. 3. If neither works, the contact is not called. A contact is also not called when their timezone does not fit their phone number's country. Nedzo never guesses a timezone from the phone number. Fix these contacts by setting a timezone on the contact record. **Specific timezone.** Pick one timezone, like your office's, and the whole campaign follows it. Every contact is called inside the calling days and hours in that timezone, wherever they are. Contacts with no timezone of their own are called too, instead of being skipped. * Pick a city timezone, like *America/New\_York*. UTC and fixed offsets are not allowed: *"Pick a city timezone. UTC and fixed offsets are not allowed."* * When you switch to **Specific timezone**, the picker starts on your browser's timezone. * Changing the timezone on a live campaign takes effect right away, the same as changing its hours. * If you switch back to **Contact's timezone**, contacts that were only callable because of the campaign's timezone are stopped again. Use **Specific timezone** when your team needs to be around for the calls, for example to take transfers during office hours. ## Daily usage cap The cap counts **connected talk minutes**, rounded up per call. It does not count calls that never connect. * When the cap is used up, the campaign moves to **Capped** and starts no new calls. * Calls already running are never cut off, so the total can go a little over the cap. * A capped campaign goes back to **Live** on its own on its next calling day. * If you raise the cap above the minutes already used today, the campaign goes back to **Live** and starts calling again inside its calling hours. * The cap's day runs midnight to midnight in your workspace timezone. Minutes used on a non-calling day count toward the most recent calling day, so no extra cap opens. Each call is also limited by the agent's **Max call duration**. See [How Max call duration works](/agents/voice-agents#how-max-call-duration-works). ## Attempts and follow-ups * A contact gets at most **Maximum follow-ups + 1** calls. * Nedzo waits at least 5 hours between two calls to the same contact. Calls from other campaigns, workflows, and manual dials count toward this gap too. * Contacts with the fewest attempts are called first. Among those, the one who has waited longest goes first. How the last call ended decides what happens next: | Outcome | Next step | | - | - | | Follow up, No answer, Voicemail, Failed, Other | Called again, up to the attempt limit | | Appointment booked, Booking failed, Transferred, Not interested, Disqualified, Wrong number, Do not disturb, Callback requested | Not called again by this campaign | Nedzo does not schedule callbacks. A contact whose call ended in *Callback requested* lands in the **Waiting on callback** filter on the campaign's page, so your team can follow up. ## Running several campaigns at once Campaigns on the same account share its call slots. Each time a slot frees up, the campaign with the fewest calls in progress gets it, so live campaigns split the slots about evenly. A new campaign gets its share even while older ones are busy. A campaign with no one to call right now leaves its slots to the others. When you pause a campaign, calls already in progress finish. Each call frees its slot as soon as it ends. ## Who gets enrolled Enrolment comes only from the saved view. You cannot add a contact to a campaign by hand. * A regular sync pulls in everyone the view matches. * A contact who stops matching the view is stopped with the reason *No longer in the view*. * If the view is deleted, the campaign stops calling and the list shows **View unavailable**. To change who is in a campaign, change the view's filter. ## Start, pause, and delete Each row on the Campaigns page has an on/off switch next to its status. | Status | Meaning | | - | - | | **Draft** | Not dialling yet | | **Live** | Calling contacts inside its days and hours | | **Paused** | Stopped by hand | | **Capped** | Daily minutes spent. Resumes on the next calling day. | | **Out of funds** | The account has no included minutes or wallet balance left. Resumes on its own when it can pay again. | | **Completed** | Finished. It cannot be switched back on. | Hover a status to see what it means and what fixes it. * Switching on a **Draft** or **Paused** campaign makes it **Live**. * Switching off a **Live**, **Capped** or **Out of funds** campaign makes it **Paused**. * **Delete** in the **…** menu stops the campaign and removes it. This cannot be undone. ## Out of funds Before a campaign claims a contact, Nedzo checks that the account can pay for a call. It can pay when it has included minutes left, money in the wallet, or [auto top-up](/billing/managing-your-subscription#auto-top-up) that refills the wallet. When the account can't pay, the campaign moves to **Out of funds**: * No contact is claimed and no call is placed, so contacts don't pile up failed attempts. * The contacts stay in the campaign, with their attempts unchanged. * The campaign goes back to **Live** on its own once the account can pay again. That happens when you top up, turn on auto top-up, or a new billing period brings back your included minutes. To fix it, open **Settings > Subscription > Billing** and add funds on the **Wallet** tab, or turn on auto top-up. See [Wallet](/billing/managing-your-subscription#wallet). A workspace that reaches its [monthly wallet spend limit](/billing/workspace-limits) also stops placing campaign calls, while the rest of the account keeps calling. Its campaigns keep their status and start calling again when the limit resets or the account owner raises it. ## Campaign list | Column | What it shows | | - | - | | Status | The status and on/off switch | | Name | Opens the campaign's page | | Agent | The agent placing calls | | View | The saved view it enrols from | | Remaining | Contacts still to be called. Turns amber at 0, meaning nobody is left to call in the view. | | Dials | Every call placed, all time | | Pickups | Answered calls: any outcome other than No answer or Voicemail | | Booked | Calls that ended in Appointment booked | | Minutes today | Minutes used against the daily cap, for example `88/133` | ## Campaign page Click a campaign's name to see everyone it enrolled. Filter the list by **All**, **Callable**, **Waiting on callback**, **Skipped**, or **Stopped**. | Column | What it shows | | - | - | | Contact | Name and phone, with a badge when the contact is skipped or stopped | | Attempts | Calls placed to this contact so far | | Last outcome | How the last call ended, or *Not called yet* | | Next attempt | When the next call is due, for example *in 3h* or *due now*, or *Stopped* | **Skipped** contacts can't be dialled. The badge gives the reason: *No phone number*, *No timezone*, *On Do Not Disturb (DND)*, or *Suppressed*. **Stopped** contacts were *Removed by hand*, *Stopped over the API*, or dropped because they are *No longer in the view*. To stop calling one contact, click the trash icon on their row and confirm **Remove**. Only this campaign stops calling them. Other campaigns can still reach them, and it is not the same as Do Not Disturb (DND). ### Draft campaigns A draft has nobody enrolled yet. Its page shows a preview of who it will call when you switch it on: everyone the view matches now. Filter it by **All**, **Will be called**, **Skipped**, or **Not called**. A line at the top shows how many contacts are in the view, how many will be called, and how many are skipped and why. The **When it goes live** column shows *Will be called* or the reason a contact will be skipped. The preview is read-only, so you can't remove anyone from it. To change who is in it, change the view's filter. ## Example A dental office wants to fill cancelled slots next week. 1. On Contacts, save a view of patients tagged `recall-due`. 2. Create a campaign with the office's booking agent and that view. 3. Set the daily cap to 120 minutes and follow-ups to 2. 4. Pick Mon–Fri and 10:00 AM – 6:30 PM. 5. Switch the campaign on. Each patient gets up to three calls, at least 5 hours apart, between 10:00 AM and 6:30 PM their time. The campaign stops for the day once it has used 120 talk minutes. # SIP Trunk Source: https://docs.nedzo.ai/concepts/enterprise-sip-trunk VIP customers can connect their own SIP trunk to bypass per-minute telephony cost while calls still reach the AI. VIP plan only. # SIP Trunk The SIP trunk connection is available on the **VIP plan only**. On other plans the feature is visible but locked, with a button that opens a short in-app form. Tell us about your setup and the team follows up. No email needed. Connect your **own SIP trunk** to Ned so your existing telephony carries the calls — bypassing our per-minute PSTN cost — while inbound calls still reach your AI agent exactly as they do today. ## How it works Your SIP trunk sends calls to a dedicated, IP-authenticated SIP connection we provision for your account. That connection is bound to Ned's voice engine, so an inbound trunk call arrives as a normal inbound call and the AI assistant starts on the leg — no change to how your agents behave. ## Prerequisites To connect a SIP trunk you'll need: * **One or more static public IPv4 addresses** for your SIP infrastructure — connections are authenticated by IP allowlist. CIDR ranges and dynamic IPs are not supported; each source must be a single IPv4 address. * **TLS 1.2 or 1.3** for SIP signaling and **SRTP** for media (encrypted end-to-end). * A trunk that supports at least one of the following codecs: **G.722, G.711 µ-law (PCMU), G.711 A-law (PCMA), G.729, Opus**. * You operate your own SIP trunk and carrier; Ned does not resell the underlying telephony. ## What it does — and doesn't — save The savings are on **telephony only** — the per-minute PSTN cost your trunk replaces. The **AI/media** portion of a call (speech-to-text, the model, and text-to-speech) is still billed as usual. SIP is economical for **high-volume** traffic; for low usage a standard number is usually cheaper. ## Requesting it on another plan Open **Phone Numbers**, choose **Add number**, then **Configure SIP trunk**, and send the form. It files your enquiry for the team, and you'll get a reply about moving to VIP. Submitting again while a request is still open won't create a duplicate. ## Setting it up 1. On the **VIP plan**, open **Phone Numbers**, choose **Add number**, then **Configure SIP trunk**. 2. Enter your static source **IPv4 address(es)** (one per line) and select **Connect SIP trunk**. 3. Point your SIP trunk at the connection details we provide and place a test call to confirm it reaches your agent. 4. To change the allowlist later, use **Edit IPs**; to remove the trunk, use **Disconnect**. ## Security * **IP authentication** — only calls from your allowlisted IPs are accepted. * **Encrypted signaling + media** — TLS + SRTP. * Standard anti-toll-fraud protections apply; outbound is rate-limited per source IP. # Cal.com integration Source: https://docs.nedzo.ai/integrations/calcom Connect Cal.com to Nedzo so AI agents can check availability and book meetings during voice calls. Authenticate with an API key and select events. Connect your Cal.com account to let AI agents check your availability and book meetings during calls. ## What it does * **Availability checks** — Agents see your real-time Cal.com availability * **Meeting booking** — Book meetings directly through Cal.com during calls * **Rescheduling** — Move existing bookings to a new time * **Cancellation** — Cancel a booking on request * **Event types** — Agents can reference your different meeting types * **Team event types** — Round-robin and other team events show up alongside your personal ones ## Personal and team event types Nedzo lists both your own event types and the ones belonging to teams you are a member of. Team events are labelled as such, so a round robin shared with your sales team is easy to tell apart from a one-to-one you own. Pick either kind when you set up an agent or a workflow calendar action. Booking a team event follows that team's own assignment rules in Cal.com, so a round robin still rotates across its members. ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Cal.com** and click **Connect** 3. Sign in with your Cal.com account and authorize access 4. You'll be redirected back to the dashboard once connected ## Using with agents Once connected, enable calendar actions on any agent: 1. Go to your agent's settings 2. Under **Calendar**, select your Cal.com connection 3. The agent can now check availability and book meetings during calls ### Picking a calendar When you open the **Calendar** dropdown in a procedure's *Book a meeting* action, your event types are grouped under two headings: * **Personal** — event types you own * **Team** — event types your team hosts and you have access to Personal always comes first, and the order holds no matter what order Cal.com returns them in. If you have no team event types, the list stays flat with no headings. Which section a calendar sits in doesn't change anything about the booking — picking the same calendar saves the same thing it always did. ## Using in workflows Add a **Calendar Action** node to any workflow to: * Check availability for a time range * Fetch available booking slots * Schedule or reschedule meetings ## Permissions Nedzo requests the following Cal.com permissions: * **Read event types** — To list the meeting types an agent can book * **Read availability** — To find open slots before booking * **Read and write bookings** — To create, reschedule, and cancel appointments * **Read teams and team event types** — To offer your team calendars, such as round-robin and collective meetings ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Cal.com card You can also revoke access from your Cal.com account settings. # Calendly integration Source: https://docs.nedzo.ai/integrations/calendly Connect Calendly to Nedzo so AI agents can check real-time availability and share scheduling links during voice calls. Uses a personal access token. Connect your Calendly account to let AI agents check your availability and share scheduling links during calls. ## What it does * **Availability checks** — Agents see your real-time Calendly availability * **Scheduling links** — Share your Calendly booking page during conversations * **Event type access** — Agents can reference your different meeting types * **Calling windows** — Avoid calling when your Calendly shows you're booked ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Calendly** and click **Connect** 3. Sign in with your Calendly account and authorize access 4. You'll be redirected back to the dashboard once connected ## Using with agents Once connected, enable calendar actions on any agent: 1. Go to your agent's settings 2. Under **Calendar**, select your Calendly connection 3. The agent can now check your Calendly availability during calls and offer booking times ### Picking a calendar When you open the **Calendar** dropdown in a procedure's *Book a meeting* action, your event types are grouped under two headings: * **Personal** — event types you own * **Team** — event types your team hosts and you have access to Personal always comes first, and the order holds no matter what order Calendly returns them in. If you have no team event types, the list stays flat with no headings. Which section a calendar sits in doesn't change anything about the booking — picking the same calendar saves the same thing it always did. ## Using in workflows Add a **Calendar Action** node to any workflow to: * Check availability for a time range * Fetch available booking slots * Schedule or reschedule meetings ## Permissions Nedzo requests read access to your Calendly account, including: * **Event types** — To know what meetings you offer * **Availability** — To check when you're free * **Scheduled events** — To avoid double-booking ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Calendly card You can also revoke access from your [Calendly integrations page](https://calendly.com/integrations). # Email integration Source: https://docs.nedzo.ai/integrations/email Set up a custom email domain in Nedzo to send branded emails from workflows. Configure DNS records, verify your domain, and track replies. Set up a custom email domain to send branded emails from your workflows. Replies are tracked in Unibox. Looking for a support inbox that turns incoming email into Unibox conversations without per-agent setup? See the [Email channel](/integrations/email-channel) instead — it's a separate, simpler setup. Turning it on needs a plan on Nedzo's current pricing. ## What it does * **Custom sender domain** — Send emails from your own domain (e.g., `notifications@yourdomain.com`) * **Per-agent inbound addresses** — Give each chat agent its own address on the verified domain (e.g. `support@mail.yourdomain.com`, `billing@mail.yourdomain.com`) so inbound emails route to the right agent * **Workflow emails** — Send automated emails as part of any workflow * **Reply tracking** — Inbound replies appear in Unibox alongside other conversations * **Variable substitution** — Personalize emails with contact data ## How to set up ### 1. Add your domain 1. Go to **Settings > Integrations** in your dashboard 2. Find **Email** and click **Configure** 3. Enter your sending domain (e.g., `mail.yourdomain.com` or `notifications.yourdomain.com`) ### 2. Add DNS records After adding your domain, Nedzo shows you DNS records to add at your domain provider: | Record Type | Purpose | | - | - | | **TXT** | Domain ownership verification | | **CNAME** | Email authentication (DKIM) | | **MX** | Inbound reply handling | Add these records in your domain registrar's DNS settings (GoDaddy, Cloudflare, Namecheap, etc.). ### 3. Verify 1. After adding the DNS records, go back to your dashboard 2. Click **Check** to verify the records 3. DNS propagation can take up to 48 hours, but usually completes within a few minutes Once verified, you're ready to send emails. ## Per-agent inbound addresses Once your domain is verified, each chat agent can claim its own local part on that domain. Open the agent → **Settings** tab → enable the **Email** channel → enter a local part in **Inbound Email Address**. The full inbound address is the local part you set + `@` + your verified sending domain. For a domain `mail.yourdomain.com`, an agent with local part `support` receives emails sent to `support@mail.yourdomain.com`. **Rules:** * Lowercase letters, numbers, dots, hyphens, and underscores * Max 64 characters * No leading, trailing, or consecutive dots * Unique per workspace — two agents in the same workspace can't share the same local part See [Chat agents → Email channel](/agents/voice-agents#email-channel) for full per-agent setup. ## Using in workflows Add a **Send Email** node to any workflow: 1. **Recipient** — Send to the trigger contact's email or enter a custom address 2. **From name** — The sender name that appears in the inbox 3. **Subject** — Email subject line (supports variables) 4. **Body** — Email content (supports variables) **Example:** ``` Subject: Thanks for chatting with us, {{first_name}} Body: Hi {{first_name}}, thanks for your time today. We'll follow up with more details shortly. ``` ## Available variables | Variable | Description | | - | - | | `{{first_name}}` | Contact's first name | | `{{last_name}}` | Contact's last name | | `{{email}}` | Contact's email | | `{{phone}}` | Contact's phone number | Any data passed as trigger variables is also available using `{{variable_name}}`. ## Default domain If you don't set up a custom domain, emails are sent from `nedzo-mail.com`. Setting up your own domain improves deliverability and lets recipients recognize your brand. ## Daily send limits Each workspace has a daily email send cap to protect deliverability and prevent runaway workflows. The cap covers all outbound paths combined — workflow Send Email actions, agent auto-replies, and manual sends from Unibox. When you approach the cap, a banner appears in the dashboard with the current usage and the daily limit. Once the cap is hit, additional sends are blocked for the rest of the UTC day and clearly surfaced as failed in workflow execution logs and the conversation timeline. The counter resets at midnight UTC. If you need a higher limit, contact support. ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Remove** on the Email integration to delete your domain configuration # Email channel Source: https://docs.nedzo.ai/integrations/email-channel Turn on the Email channel to receive support email into Unibox, reply from your own address, and let Ned auto-reply to customers by email. The Email channel gives your workspace a dedicated intake address, so email forwarded from your existing support inbox turns into conversations in Unibox — no per-agent setup required. Turning the channel on needs a plan on Nedzo's current pricing. Once the channel is provisioned, sending is not plan-gated. This is different from the [Email integration](/integrations/email), which sets up a custom sending domain for **workflow** emails and per-agent inbound addresses. The Email channel is a first-class conversation channel alongside voice, chat, SMS, and WhatsApp — set it up from **Settings > Channels > Email**. ## How it works 1. Nedzo gives your workspace a unique **intake address** (`ws-xxxxxxxxxxxx@nedzo-mail.com`). 2. You forward your real support inbox (e.g. `support@yourcompany.com`) to that intake address using your email provider's admin console. 3. Incoming mail creates or continues a conversation in Unibox, with the true original sender captured as the contact — even through a forwarding rule. 4. You (or Ned, if enabled on the agent) reply from Unibox. Replies go out from your workspace address by default, or from your own domain once you authenticate it. ## Setting it up Go to **Settings > Channels > Email**. ### 1. Enable the channel Click **Enable email channel**. Nedzo provisions your intake address immediately — no configuration needed to start receiving. You can regenerate the intake address at any time (for example, if it leaked). The old address keeps working for **72 hours** after regenerating so you have time to update your forwarding rule, then it stops routing. ### 2. Set your reply email Enter the address your customers actually email and that you'll reply from — e.g. `support@yourcompany.com`. This is the address forwarding rules point away from and toward the intake address. Free/personal email providers (Gmail, Outlook, Yahoo, iCloud, and similar) aren't accepted here — the reply address must be on a domain you control. Changing the reply address later clears any prior forwarding verification, since it's a different mailbox to verify. ### 3. Sender name Set the display name recipients see on emails Ned or your team sends (e.g. "Acme Support"). This is used for every outbound email on the channel and shown next to sent messages in Unibox. ### 4. Set up forwarding Auto-forward your reply address to the intake address using your provider's admin console: **Google Workspace** 1. Sign in to the [Google Admin console](https://admin.google.com) as an administrator. 2. Go to **Apps > Google Workspace > Gmail > Routing**. 3. Add a routing rule for your support mailbox and use **Also deliver to** to add your intake address as an additional recipient. 4. Save. **Microsoft 365** 1. Sign in to the [Exchange admin center](https://admin.exchange.microsoft.com) as an administrator. 2. Go to **Recipients > Mailboxes** and select your support mailbox. 3. Open **Mailbox > Manage mail flow settings > Email forwarding**. 4. Enable forwarding to your intake address, keep a copy of forwarded messages, and save. Forwarding is verified once a one-time token round-trips through your actual forward: a real support email creates a conversation, or Gmail/M365's forwarding-confirmation email is captured automatically (its code or link is stored against your intake address so you don't have to hunt for it). ### 5. Send from your own address (optional) By default, replies send from your workspace intake address. To send from your own address (e.g. `support@yourcompany.com`) instead: 1. Click **Authenticate your domain**. 2. Add the DNS records Nedzo shows you (SPF TXT + DKIM CNAMEs) at your domain host. 3. Click **Validate DNS** once you've added them. DNS changes can take up to 48 hours to propagate. Once verified, replies go out `From:` your real address with proper SPF/DKIM alignment. A domain left unverified for **14 days** is marked failed — start authentication again when you're ready. **DMARC:** Nedzo also shows a recommended DMARC record for your domain and reports whether one is present. This is advisory only — a missing or misconfigured DMARC record never blocks sending, but publishing one improves deliverability and guards against spoofing. ## Replying and AI auto-reply * Replies from Unibox thread correctly (In-Reply-To/References) whether or not your domain is authenticated. * If the agent handling the conversation has AI enabled, Ned can auto-reply to inbound emails on the channel directly, using the same knowledge base, prompt, and actions as your other channels. * To keep a sender's mail in Unibox without the AI answering it, add a **No AI reply** rule for the address or its domain. A **Block** rule discards the mail instead. See [Blocking a sender](/unibox/conversations#blocking-a-sender). * Out-of-office auto-replies and mailing-list mail are recognized and never trigger an auto-response loop, but still appear in Unibox for a human to see if needed. Mail that loops back to our own system (or bounce/postmaster chatter) is dropped automatically. ## Suppressions If your own authenticated domain has bounces, spam complaints, or unsubscribes on file with Mailgun, view and clear them from the channel settings (only entries on your own domain — never other workspaces'). A suppressed recipient is blocked with a clear message rather than silently dropped. ## Limits Each workspace has a send-rate limit to protect deliverability; if you hit it, sends are rejected until the limit window resets. If you're consistently hitting it, contact support. # GoHighLevel integration Source: https://docs.nedzo.ai/integrations/gohighlevel Integrate Nedzo with GoHighLevel to trigger AI voice calls from GHL workflows. Sync contacts, map custom fields, and automate outbound calls. Connect GoHighLevel (GHL) to trigger AI voice calls directly from your GHL workflows using the "Make Nedzo Call" action. ## What it does * **Workflow action** — Add a "Make Nedzo Call" step to any GHL workflow * **Agent selection** — Pick which Nedzo agent handles the call from a dropdown * **Contact sync** — GHL contact data is passed to Nedzo automatically ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **GoHighLevel** and click **Connect** 3. Sign in with your GHL account 4. Select the GHL location to connect to your Nedzo workspace 5. You'll be redirected back once connected Each GHL location maps to one Nedzo workspace. ## Using in GHL workflows Once connected, a new action becomes available in your GHL workflow builder: ### 1. Add the action 1. Open any workflow in GHL 2. Add a new action step 3. Select **Make Nedzo Call** ### 2. Configure the action | Field | Required | Description | | - | - | - | | Agent | Yes | Select which Nedzo agent handles the call | | Phone Number | Yes | Contact's phone number (E.164 format) | | First Name | No | Contact's first name | | Last Name | No | Contact's last name | | Email | No | Contact's email address | | Business Name | No | Contact's company name | The **Agent** dropdown automatically loads your Nedzo agents for the connected workspace. ### 3. Map contact fields Use GHL's built-in contact variables to map fields: ``` Phone Number: {{contact.phone}} First Name: {{contact.firstName}} Last Name: {{contact.lastName}} Email: {{contact.email}} Business: {{contact.companyName}} ``` ## Contact sync When a call is triggered from GHL: * If the contact doesn't exist in Nedzo, it's created automatically * If the contact already exists (matched by phone or email), it's updated with the latest GHL data * Custom fields from GHL are synced to Nedzo contact fields ## Authentication The integration uses OAuth — no API keys needed. The GHL `locationId` identifies which workspace to use for each call. ## Error handling | Error | Cause | Fix | | - | - | - | | Location not connected | GHL location isn't linked to a Nedzo workspace | Reconnect from the Nedzo dashboard | | Agent not found | Selected agent was deleted | Update the workflow action to pick a different agent | | Invalid phone number | Phone isn't in E.164 format | Ensure GHL stores phones as `+14155551234` | ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the GoHighLevel card This stops all GHL workflow actions from triggering Nedzo calls for that location. # Google Calendar integration Source: https://docs.nedzo.ai/integrations/google-calendar Connect Google Calendar to Nedzo so AI agents can check real-time availability and book meetings during calls. Supports multiple calendars. Connect your Google Calendar to let your AI agents check your real-time availability and book meetings directly during calls. ## What it does * **Availability checks** — Agents know when you're free or busy before suggesting times * **Meeting booking** — Book meetings on your calendar directly from a call * **Rescheduling** — Move existing bookings to a new time * **Calling windows** — Skip calling contacts when your calendar shows you're busy ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Google Calendar** and click **Connect** 3. Sign in with your Google account and grant calendar access 4. You'll be redirected back to the dashboard once connected ## Configuration After connecting, you can configure your availability settings: | Setting | Description | Default | | - | - | - | | Calendar | Which Google Calendar to use | Primary calendar | | Available days | Days of the week you're available | Monday – Friday | | Available hours | Time window for bookings | 9:00 AM – 5:00 PM | | Slot duration | Length of each meeting slot | 30 minutes | | Timezone | Timezone for availability | Your local timezone | These settings are configured per agent, so different agents can use different availability windows. ## Using with agents Once connected, you can enable calendar actions on any agent: 1. Go to your agent's settings 2. Under **Calendar**, select your Google Calendar connection 3. Configure the availability window for that agent 4. The agent will automatically check your calendar during calls ## Using in workflows Add a **Calendar Action** node to any workflow to: * Check availability for a specific date/time * Book a meeting with a contact * Reschedule an existing booking ## Permissions Nedzo requests the following Google Calendar permissions: * **View your calendars** — To list available calendars * **View and edit events** — To check free/busy times and create bookings * **View free/busy information** — To determine availability without reading event details ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Google Calendar card 3. This removes the connection and any agent calendar configurations tied to it You can also revoke access from your [Google Account permissions page](https://myaccount.google.com/permissions). # Instagram integration Source: https://docs.nedzo.ai/integrations/instagram Connect one or more Instagram Business or Creator accounts to Nedzo. Sync DMs into Unibox, give each account its own AI chat agent, and manage conversations. Connect your Instagram Business or Creator accounts to sync DMs into Unibox and respond with AI. A workspace can connect several accounts, and each one can have its own chat agent. This helps agencies and businesses with more than one location. ## What it does * **DM sync** — Instagram direct messages appear in Unibox alongside calls and other conversations * **AI responses** — Your agents can respond to Instagram DMs automatically * **Unified inbox** — Manage all conversations from one place * **Several accounts** — Connect more than one Instagram account in the same workspace, each with its own agent ## Requirements * An **Instagram Business** or **Instagram Creator** account * A **Facebook Page** linked to your Instagram account * Admin access to the Facebook Page Instagram's API works through Facebook Pages, so you'll connect via Facebook first. ## How to connect ### 1. Connect Facebook 1. Go to **Integrations** in the sidebar 2. Open **Instagram** and click **Connect Instagram** 3. Sign in with your Facebook account 4. Grant access to your Facebook Pages ### 2. Pick the account to add After connecting Facebook, you'll see a list of your Facebook Pages that have a linked Instagram account: 1. Select the Page connected to the Instagram account you want to add 2. Click **Save Page** 3. Your Instagram DMs will start syncing to Unibox Changed your mind? Click **Cancel** to stop the setup. If the list is empty, no Page on that Facebook login has a linked Instagram account. Click **Reconnect Facebook** to sign in with a different login. ### Add another account Once one account is connected, the Instagram panel lists every connected account. Click **Add Instagram account** and go through the same two steps for the next one. Each row shows: * The Instagram handle, for example `@acmedental` * The Facebook Page it goes through * The chat agent it is assigned to, or **No agent assigned** * **Needs reconnect** when the connection stopped working. Add the account again to fix it. ### 3. Assign an agent An account answers with AI only once it has a chat agent: 1. Open the chat agent and go to its **Settings** tab 2. Under **Channels**, pick the Instagram account 3. Save the agent Each Instagram account goes to one chat agent, and a chat agent has at most one Instagram account. See [Chat agent channels](/agents/voice-agents#channels). ## How it works Once connected: 1. New Instagram DMs are received via webhook and appear in Unibox 2. Each DM goes to the agent assigned to the account that got it 3. You can view and reply to messages directly from the dashboard 4. If an AI agent is assigned, it can respond automatically 5. All conversation history is tracked in one thread per contact Unibox and Conversation Logs show which Instagram account each conversation came through, so your team knows which business the contact wrote to. ## Permissions Nedzo requests Facebook permissions to: * **View your Pages** — To list pages with linked Instagram accounts * **Manage Page messages** — To send and receive Instagram DMs through the Page * **Access Instagram account info** — To identify the linked Instagram account ## Disconnecting 1. Go to **Integrations** and open **Instagram** 2. Click **Disconnect** on the account's row This removes that one account. Your other Instagram accounts keep working. It keeps your Facebook OAuth. To fully disconnect, also remove the Facebook connection. You can revoke app access from your [Facebook Business settings](https://business.facebook.com/settings/apps). # Integrations Source: https://docs.nedzo.ai/integrations/overview Connect Nedzo with Slack, Google Calendar, Calendly, Cal.com, GoHighLevel, Instagram, and email. Sync contacts, trigger workflows, and push call data. Connect Nedzo to the tools you already use. Sync contacts, trigger workflows, and push call data to your CRM, calendar, and more. ## Communication Turn your support inbox into Unibox conversations Send emails from your own domain Sync Instagram DMs to Unibox Send notifications to channels ## Calendars Check availability and schedule calls Open-source scheduling integration Sync availability and book meetings ## CRM Trigger calls from GHL workflows ## Cancelling a connection If you press **Cancel** on a provider's sign-in or consent screen, Nedzo takes you back to the **Integrations** page. The integration stays disconnected and nothing is saved. Connect it again whenever you are ready. # Slack integration Source: https://docs.nedzo.ai/integrations/slack Connect Slack to Nedzo and send real-time call notifications, summaries, and workflow updates to your team's channels. Set up in minutes with OAuth. Connect Slack to send real-time notifications and call summaries to your team's channels. ## What it does * **Call notifications** — Post updates to a channel when calls complete * **Workflow messages** — Send custom messages to Slack from any workflow * **Variable substitution** — Include contact details and call data in messages * **Channel selection** — Pick which channel to post to per workflow ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Slack** and click **Connect** 3. Sign in to your Slack workspace and authorize access 4. You'll be redirected back to the dashboard once connected ## Using in workflows Add a **Slack Message** node to any workflow: 1. Select the Slack channel to post to 2. Write your message — use variables like `{{first_name}}`, `{{phone}}`, or any trigger data 3. The message is sent when the workflow reaches that step **Example message:** ``` Call completed with {{first_name}} {{last_name}} ({{phone}}). ``` ## Available variables You can use these variables in your Slack messages: | Variable | Description | | - | - | | `{{first_name}}` | Contact's first name | | `{{last_name}}` | Contact's last name | | `{{email}}` | Contact's email | | `{{phone}}` | Contact's phone number | Any data passed as trigger variables is also available using `{{variable_name}}`. ## Channel access After connecting, Nedzo can post to: * **Public channels** — Nedzo automatically joins the channel when posting * **Private channels** — You need to manually invite the Nedzo app to the channel using `/invite` in Slack ## Token refresh Slack tokens expire periodically. Nedzo automatically refreshes them in the background. If a refresh fails, the integration will show a **Reconnect** prompt in your dashboard — just click it to re-authorize. ## Permissions Nedzo requests the following Slack permissions: * **chat:write** — To post messages to channels * **channels:read** — To list available public channels * **channels:join** — To join public channels when posting * **groups:read** — To list private channels you've invited Nedzo to ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Slack card You can also remove the app from your [Slack workspace settings](https://slack.com/apps/manage). # WhatsApp integration Source: https://docs.nedzo.ai/integrations/whatsapp Connect WhatsApp to Nedzo. Sync messages into Unibox, respond with AI chat agents, and keep using the WhatsApp Business app on the same number with Coexistence. Connect a WhatsApp number to sync messages into Unibox and respond with AI. ## What it does * **Message sync** — WhatsApp messages appear in Unibox alongside calls, email, and other channels * **AI responses** — Your agents can reply to WhatsApp messages automatically * **Coexistence** — Keep using the WhatsApp Business app on your phone with the same number * **History import** — Bring up to about 6 months of past conversations with you when you connect ## Two ways to connect Pick based on whether the number is already in use on a phone. | | Coexistence | New number | | - | - | - | | **Use it when** | The number already runs in the WhatsApp Business app | The number is not on WhatsApp yet | | **Phone app keeps working** | Yes | No | | **Past conversations import** | Yes, about 6 months | Nothing to import | | **Contacts import** | Yes, from the phone's address book | No | Coexistence is the default. Most businesses connecting to Nedzo already have a number running in the WhatsApp Business app and do not want to give it up. ## Requirements * A **Meta Business account** * A phone number you control * For Coexistence: the number already active in the **WhatsApp Business app**, on a recent version * Admin access to the Meta Business account ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **WhatsApp** and click **Connect** 3. Sign in with Facebook and pick your Meta Business account 4. Choose the number you want to connect 5. Verify the number when prompted Nedzo opens Meta's own signup flow, so your credentials go to Meta and never to us. ### Verifying the number Meta sends a verification code to the number. Enter it in the **Verify your WhatsApp number** step to finish connecting. ## How Coexistence works Once a Coexistence number is connected, three things flow into Nedzo that a normal connection does not carry: * **History** — past conversations on that number, delivered in chunks over the first few minutes. Older threads appear in Unibox as they arrive. * **Message echoes** — messages your team sends *from the phone app* are copied into Nedzo, so the AI and your operators see the whole thread, not just the half that came through the dashboard. * **Contact sync** — contacts added, changed, or removed in the phone's address book update in Nedzo too. This means someone can answer a customer on their phone and the agent still knows what was said. ### How phone replies look in Unibox A reply sent from the WhatsApp Business app shows in Unibox with a smartphone icon and the label **Sent from WhatsApp app**. This applies to new replies and to replies in imported history. * They are never marked as sent by the AI, so AI-only actions such as **Improve answer** do not appear on them. * WhatsApp does not tell Nedzo which teammate sent them, so no name is shown. ## How it works 1. New WhatsApp messages arrive by webhook and appear in Unibox 2. You view and reply from the dashboard, or from the phone app if you use Coexistence 3. If an AI agent is configured, it can reply automatically ## Media WhatsApp conversations support images, video, and audio in both directions. Use the paperclip in the composer to attach files up to 16 MB. See [Unibox channels](/unibox/channels) for the full rules. ## Reply windows WhatsApp enforces its own messaging window. If too much time has passed since the contact's last message, you cannot send a free-form reply until they message again. This applies to media the same way it applies to text. ## Disconnecting Disconnect from **Settings > Integrations**. Meta releases the number on its side, which is not instant — if you try to reconnect right away you may see: > WhatsApp is still releasing this number on Meta's side. Please wait before reconnecting. Wait a few minutes and try again. ## Troubleshooting **The number is already registered elsewhere.** A WhatsApp number can only belong to one Meta Business account at a time. Remove it from the other account first. **History did not import.** History only comes with Coexistence, and only for numbers that were already active in the WhatsApp Business app. A brand-new number has nothing to import. **Messages sent from the phone are missing.** Message echoes are a Coexistence feature. A number connected as a new number has no phone app attached, so there is nothing to echo. # Introduction Source: https://docs.nedzo.ai/introduction Nedzo is an AI customer engagement platform for deploying voice, chat, SMS, and email agents. Explore guides, API references, integrations, and automation. Nedzo is an AI customer engagement platform that deploys AI agents to resolve customer conversations across voice, chat, SMS, and email — all from one platform. This documentation covers everything you need to build, deploy, and refine your AI agents. ## What you can do with Nedzo Build agents with your brand voice and business rules across voice, chat, SMS, and email Automate follow-ups, CRM updates, and notifications based on conversation outcomes Manage all customer conversations across every channel in one inbox Connect your calendar, CRM, Slack, email, and more ## Getting started 1. **Build** — Configure your agents with custom prompts, voices, and business rules 2. **Deploy** — Activate across voice, chat, SMS, and email channels 3. **Refine** — Track metrics and optimize using conversation analytics ## Need help? Join the Nedzo community Contact our support team # Contact memory Source: https://docs.nedzo.ai/settings/contact-memory Let your agents remember a contact's details and open follow-ups between conversations. Works for every voice, chat, and web agent in the workspace. Contact memory lets your agents remember a contact from one conversation to the next. The agent keeps the contact's details and any open follow-ups, and greets a returning contact with that in mind. When memory is off, every conversation starts fresh. Memory works for every agent in the workspace: voice, chat, and web agents, on every plan. ## Turn it on or off 1. Open **Settings > Workspace > General**. 2. Find the **Memory** section. 3. Switch **Contact memory** on or off. The switch covers the whole workspace. Only an account owner or admin can change it. A change takes effect from each contact's next conversation. ## How it works * **After a conversation ends**, the agent writes down what it learned about the contact. * **Before the next conversation**, the agent reads it, so it doesn't ask for the same things again. * **It works across channels.** What a contact said on a call is there when they text or use your website chat. ## Searching past conversations When a returning contact asks about something from an earlier conversation, the agent searches that contact's past conversations first. It does this before it answers, so it does not guess or ask the contact to repeat themselves. For example, a contact who asks *"So, is it fixed now?"* gets an answer based on what was said last time. The agent does not search on a greeting, small talk, new information, or a general question about your business. It also does not search for something said earlier in the same conversation. What the agent can search: * **Only this contact's conversations.** It never sees another contact's history. * **Finished conversations that have a summary.** A finished call counts even if nobody closed it in [Unibox](/unibox/overview). The conversation that is still going on is never part of the search. * **Not deleted conversations.** Once a conversation is deleted, the agent can't find it. This works for every agent with memory on, voice, chat, and web. ## See what an agent remembers Open a contact in **Contacts**, or open their conversation in [Unibox](/unibox/overview) and expand the **Memory** card in the details panel. The card lists what the agent knows, including **Open loops**: things the contact is still waiting on. A new contact shows *"Ned hasn't learned anything about this contact yet."* The card fills in after their first conversation closes. ## Keep callers apart Agents recognise callers by their phone number. If your call forwarding replaces the caller's number with your own line, every call looks like the same person. Make sure your forwarding keeps the original caller's number. When Nedzo can't be sure who a caller is, the agent does not use memory on that call. One contact's details are never given to another. # Team members Source: https://docs.nedzo.ai/settings/team-members Invite teammates, give them access to workspaces, set what each one can view and manage, and remove access. Covers re-inviting someone who is already on the account. Team members are the people who can sign in to your Nedzo account. Each member has a role, a set of workspaces they can reach, and permissions that decide which pages they can view or manage. You manage members in two places: | Page | Where | Use it to | | - | - | - | | **Users** | **Settings > Admin > Users** | See everyone on the account, invite with any role, and change any member's workspaces, role, and permissions | | **Team Members** | **Settings > Workspace > Team Members** | See who can reach the current workspace, invite someone into it, and remove someone from it | Only **owners** and **admins** see the **Invite User** button and the row actions. ## Roles | Role | Access | | - | - | | **Owner** | Reaches every workspace and every setting. There is one owner per account. The owner role can't be changed or invited. | | **Admin** | Full access to all features and settings | | **User** | Standard access to the workspaces they are given, limited by their permissions | Only the owner can change another member's role. ## Invite a member 1. Open **Settings > Admin > Users** and click **Invite User**. 2. Enter an **Email Address**. **First Name** and **Last Name** are optional. 3. Pick a **Role**: User or Admin. 4. Under **Workspace Access**, pick at least one workspace. 5. For a **User**, open **Permissions** and pick what they can view and manage. 6. Click **Send Invite**. The new member gets an email with a link to set their password. The password needs at least 8 characters, with an uppercase letter, a lowercase letter and a number. They appear in the list straight away. If part of the invite did not work, Nedzo says so. When the email fails, the window stays open. Other notes show in a message after the window closes. | Message | What to do | | - | - | | The invite email didn't send. You can try again now. | Click **Send Invite** again. | | They're already on the account, so we sent their invite link again. Nothing else changed. | Edit the member to change their role or permissions. | | We couldn't add them to any workspace, so they will see an empty app. Contact support. | Contact support. | | They were added to workspaces but have no permissions yet. Edit the member to set them. | Edit the member and set their permissions. | Inviting from **Settings > Workspace > Team Members** works the same way, with two differences: the member is always invited as a **User**, and they get access to the current workspace. ### Permissions Permissions apply only to the **User** role. Admins and the owner can reach everything. Each page has a **View** switch and a **Manage** switch. | Page | What it covers | | - | - | | Dashboard | Workspace analytics and overview. View only: Manage is not available. | | Unibox | The unified inbox for all conversations | | Agents | Creating and managing AI voice agents | | Contacts | Contact lists and records | | Workflows | Creating and managing automation workflows | | Conversation Logs | Viewing and managing call conversations | | Phone Numbers | Phone numbers and DIDs | | Integrations | Third-party integrations | | Settings | Workspace settings | * Every switch starts off. * Turning on **Manage** also turns on **View**. Turning off **View** also turns off **Manage**. * Click the **View** or **Manage** header to switch the whole column. * [Campaigns](/campaigns/overview) has no permission switch. Every workspace member can use it. * **Phone Numbers > Manage** is needed to add numbers in any way: buy, port in, add a SIP trunk, or verify a caller ID. It is also needed to rename, release, or delete a number or a verified caller ID. Without it, **Add number** is greyed out, and hovering it says why. ### Seats The invite window shows how many seats you use, for example *"4 of 5 seats used."* Only members who accepted their invite count. At the limit, **Send Invite** is turned off. Buy an extra seat for \$29 a month first, with **Add seats** on the **Team seats** card in **Settings > Subscription > Billing**. See [Extra seats](/billing/managing-your-subscription#extra-seats). Unlimited plans show no seat count. ### Invite errors | Message | What it means | | - | - | | Email is required | The email box is empty | | At least one workspace must be selected | Pick a workspace under Workspace Access | | This email is already in use | The email is taken and can't be invited. The window shows Nedzo's reason when there is one. | | Only admins can invite members | Your role can't invite | | Your account has no available seats | Buy an extra seat in Billing first | ## Resend an invite If someone never accepted their invite, send it again from **Settings > Admin > Users**. Open the menu on their row and click **Resend invite**. * It shows only for members who have not set a password yet. It never shows for the owner. * The member needs access to at least one workspace. * Nedzo says what happened, the same way as a new invite. For example, if the email didn't send, you see it and can try again. * If you resend too soon after the last invite, Nedzo shows a message asking you to wait. ## Re-invite someone already on the account If you invite an email that already belongs to a member of this account, Nedzo does not send a second invite. The invite window says: > *\* is already on this account. It then offers a **Give access to \** button for the workspaces you picked. Click it to add them to those workspaces. You see *"\ now has access to \"* when it is done. * Workspaces they can already reach are skipped. * They get the permissions they already have on this account. Any permissions you set in the invite window are not used. Change them afterwards from the member list. * If you didn't pick a workspace, pick one above, or edit their access from the member list. * **Send Invite** stays off while this panel shows. Change the email to start over. This is the way to bring back someone who lost access to a workspace. ## Change a member's workspaces and permissions 1. Open **Settings > Admin > Users** and click the member. 2. In the drawer, change **Workspace Access** and **Permissions**. 3. Click **Save Changes**. How saving works: * **Permissions carry over.** When you add a workspace, the member gets the same permissions they have in their other workspaces. If they have no workspace right now, they get the permissions they had before. The drawer notes this. Only change the switches if you want something different. * **Permissions are shared.** A User's permissions apply to every workspace they can reach. * **Permissions only save when you change a switch**, or when you change the member to the User role. Adding a workspace alone keeps their current permissions. * **You can't remove yourself** from a workspace. Ask another admin to do it. * If some workspace changes fail, Nedzo says which ones and why, and does not save the permissions. Click **Save Changes** again to retry. The member gets an email each time they are added to or removed from a workspace. Every change is also written to the account's audit log. ## Remove a member from a workspace 1. Open **Settings > Workspace > Team Members** in the workspace. 2. Click **Remove** on the member's row and confirm. They lose access to that workspace only. Their permissions are kept, so if you add them back later they get the same access they had. ## Delete a member To remove someone from the whole account, open them in **Settings > Admin > Users** and click **Delete**. They lose access to every workspace. This can't be undone. You can't delete the owner or yourself. # A2P 10DLC registration Source: https://docs.nedzo.ai/telephony/a2p-10dlc-registration Register your business and campaign with US carriers so your SMS gets delivered. What you supply, what Nedzo writes and hosts for you, and what registration costs. US carriers make every business register before it can text from a local 10-digit number. That process is called **A2P 10DLC**. Unregistered traffic gets filtered or blocked, so registration is the first thing to do before you send a single message. Nedzo files it for you. You supply your business details. Nedzo writes the campaign, generates the opt-in page, and submits both to the carriers. A2P 10DLC covers **US and Canada** numbers only. Registration takes a few business days on the carrier side, so start it before you plan your first send. ## Where to start 1. Open **Phone Numbers** and select the **Messaging (A2P)** tab. 2. Click **Register for 10DLC**. 3. Work through the three steps below. You can only have one registration in progress or active at a time. The button is disabled while one is running. ## What you supply The wizard is three steps. Have these ready before you start. ### Step 1: Business details | Field | What it is | | - | - | | **Legal business name** | The name on your tax filings, not your trading name | | **Display name** | The brand name your customers know you by | | **Entity type** | Limited Liability Company, Publicly Traded Corporation, Non-profit Corporation, Sole-Proprietorship, or Government or Co-operative | | **EIN** | Your federal tax ID. Required for every entity type except Sole-Proprietorship | | **Industry** | The vertical your business sits in, such as Healthcare, Real Estate, or Retail | | **Authorised representative** | A real person at your business: first name, last name, email, and phone | | **Business address** | Street, city, state, postal code, and country | Two entity types ask for extra fields: * **Sole-Proprietorship** skips the EIN but needs a **mobile number**. Carriers send an SMS one-time code there to verify you. * **Publicly Traded Corporation** needs your **stock symbol** and **stock exchange**. Carriers check the legal name, EIN, and address against public records. One wrong character gets the brand rejected. Copy them straight off your IRS letter or state filing rather than typing from memory. ### Step 2: Campaign type and consent Pick your campaign type, then tick the consent acknowledgement. The acknowledgement covers the registration charge, the monthly campaign fee, and the fact that carriers charge again for failed campaigns and resubmissions. You do not write any campaign wording here. See [What Nedzo handles](#what-nedzo-handles). ### Step 3: Logo Upload your brand logo. Nedzo puts it on the opt-in page it generates for your filing. | Requirement | Value | | - | - | | Max size | 2 MB | | Formats | PNG, JPEG, SVG, WebP | ## What Nedzo handles ### The opt-in page Carriers want proof that people agreed to receive your texts. Most providers make you build that page yourself, host it, and hand them the link. Nedzo does it for you. It starts from a page that already passed carrier review, rebrands it with your business details and your logo, hosts it, and attaches it to your carrier filing as the consent proof. Nedzo hosts the privacy policy and terms pages the opt-in page links to as well. If you run a white-label setup with your own policy URLs, the page points at yours instead. You do not need a page on your own site and you have nothing to host, publish, or maintain. The opt-in page exists for the carrier filing. It is not a marketing page, and there is nothing for you to manage or share. ### The campaign copy The campaign description, sample messages, opt-in wording, keywords, and compliance links are standardised. Nedzo generates all of them server-side to match what carriers approve, so you do not draft them and you cannot edit them. That is what keeps rejections down. The samples describe the shape of your traffic to the carrier. They do not limit what your real messages say. ## Cost | Campaign type | Registration | Monthly campaign fee | | - | - | - | | Low-Volume Mixed | \$23.80 one time | Up to \$12/month | | High-Volume Mixed | \$54.40 one time | Up to \$12/month | Both fees are set by the carriers and passed through at cost. They show as separate lines on your invoice. See [Usage-based charges](/billing/plans#usage-based-charges) for how the rest of your telephony bills. Carriers charge the campaign fee again for failed campaigns and for every resubmission. Getting Step 1 right the first time is the cheapest path. ## Tracking the registration The **Messaging (A2P)** tab lists your registrations with a brand status and a campaign status. Nedzo registers the brand first, then the campaign. The registration is done when both read active. If either one fails, the row shows a reason from the carrier and a **Fix & Resubmit 10DLC** action. Nedzo keeps up to 1,000 characters of each reason, so the carrier's error code at the end is not cut off. If a failed row has no reason yet, Nedzo checks with the carrier again when you open the dashboard, at most every 6 hours, and fills the reason in once the carrier gives one. Correct the field the carrier flagged and submit again. Resubmitting needs a fresh logo upload, since the file is not kept between attempts. Common rejection reasons: * The legal name, EIN, or address does not match public records. * The entity type is wrong. A sole trader filed as a company gets rejected. * The authorised representative cannot be tied to the business. ## After approval Your workspace numbers can send SMS. Two things stay on you: * **Keep the opt-out footer on.** Nedzo adds it to the first message you send a new number, with your business name and STOP instructions. Carriers expect it. See [Send SMS action](/workflows/actions/send-sms#compliance). * **Honour opt-outs.** Nedzo blocks every outbound path to a contact who replied STOP, per number. See [SMS opt-out handling](/unibox/channels#opt-out-handling-tcpa). # AI controls Source: https://docs.nedzo.ai/unibox/ai-controls How a conversation hands off from the AI to a human, why the AI pauses, and how it gets handed back. When a chat agent is handling a conversation, the AI can stop responding — either because a teammate stepped in, or because something about the conversation calls for a person. ## When a teammate replies, the AI stops The moment anyone on your team sends a message in a conversation from Unibox, the AI goes quiet on that conversation. This is automatic on every channel and there is nothing to configure — a human reply means a human owns the conversation. The AI does not come back on that conversation by itself, and it stays out of it however long the conversation runs — closing the conversation does not change that, and neither does the contact writing in again days later. A human reply means a human owns the conversation until someone says otherwise. When you want the AI to take over again, open the **⋯** menu in the thread header and choose **Let the AI reply again**. The AI answers the contact's next message as normal. It applies from that moment on, not retroactively, so nothing you or the AI has already sent changes. The option carries a second line, **Paused since a teammate replied**, so you can tell what you are undoing. It is only shown to members who can manage Unibox. A view-only member does not see it. The same menu uses the words **Let the AI reply again** for a second thing: lifting a **No AI reply** rule on the sender. That option sits lower in the menu, with the sender rules, and removes the rule for every conversation with that sender. See [Blocking a sender](/unibox/conversations#blocking-a-sender). If a teammate replies again afterwards, the AI goes quiet again — you do not have to undo anything first, and handing a conversation back once does not opt it out of this permanently. ## Pause reasons The AI also stops on its own in a few situations. Whatever the cause, the conversation shows a paused band with the reason: | Reason | What happened | | - | - | | Manual reply | A team member sent a message in the conversation | | Keyword detected | The contact used a protection keyword (e.g. "manager", "human") | | Escalation requested | The contact asked for a person, or needed something the agent can't help with, so the agent handed the conversation to your team. The AI stays out of this conversation until it closes | | Booking confirmed | A calendar booking was completed | | Message limit reached | The conversation hit the message limit | | Contact opted out | The contact opted out of messages | | Profanity detected | The contact's message tripped the profanity filter | | Stopped by a procedure | The contact asked the agent to stop replying. The AI stays silent, but the conversation stays open for a person | | Out of credits | The workspace ran out of credits. The band reads **AI paused - out of credits** | | Reply loop detected | The AI was replying to an automated sender over and over. The band reads **AI paused - reply loop detected**. See [Reply loops](#reply-loops) | Where you see it: * A band at the top of the conversation, just under the header, reading **AI paused** and the reason. It is shown on open and snoozed conversations; a conversation that has ended does not show it. * A row in the conversation timeline marking the moment the AI stopped — and, if someone has handed the conversation back, a row for that too. ## Turning the AI back on It depends on who stopped it, and the difference matters: **When the AI stopped itself** — a keyword, an escalation, a booking, a message limit, profanity, a stop request from the contact, a reply loop — **marking the conversation read is the way back.** Click **Mark as read** in the thread header, and the contact's next message is answered by the AI as normal. **When a teammate replied**, marking it read is *not* enough, because the AI is staying out of a conversation a colleague is handling rather than reacting to something the contact did. Use **Let the AI reply again** in the **⋯** menu. That option shows on an open conversation that is currently showing the teammate-reply pause. On a conversation that has ended it is not there. A chat conversation does not reopen when the contact writes again: their next message starts a new conversation, and the AI answers it as normal. There is no **Unpause AI** switch, and **Let the AI reply again** is not one. It is a one-off instruction for a single conversation, not a setting, and there is nothing to switch back — the next teammate reply silences the AI again on its own. **One exception clears itself.** An **Out of credits** pause lifts as soon as the wallet is topped up. It is a workspace-wide billing state rather than a decision about that conversation, so no one has to reopen anything. **Escalations wait for your team.** When the agent escalates, your admins get an email and the AI stops replying in that conversation. The contact can keep writing, and the thread waits for a person. The conversation closes 24 hours after the escalation, gets its summary and outcome, and the contact's next message starts a new conversation that the AI answers. Your team is emailed once per conversation. The escalation email links straight to the conversation in Unibox, so whoever picks it up can reply there. **Contact opted out is different.** Marking the conversation read does not lift an opt-out. SMS opt-outs block every outbound path to that contact — including the AI — until the contact replies **START**, **UNSTOP**, or **YES**, or someone manually re-opts them in from the contact panel. Marking it read just ends this conversation; the AI still won't message that contact again until the opt-out itself clears. ## Reply loops Some senders are bots. A billing notice or a code sender can answer every message it gets with the same text. Without a guard, the AI would answer back each time, and the two would loop for as long as the bot keeps going. Nedzo watches for this on SMS, WhatsApp, Messenger, Instagram, and email. On SMS, WhatsApp, Messenger, and Instagram, the AI pauses when, inside 10 minutes: * the AI's last 3 replies were the same, or * the contact's last 3 messages were the same and the AI's last 2 replies were too, or * the AI sent 25 replies in the conversation. On email, the AI pauses after 8 auto-replies in a conversation inside 10 minutes. Case and spaces do not matter when messages are compared. Replies that failed to send are not counted, so a sending outage does not look like a loop. A contact who sends the same message a few times does not trip the guard on their own. When the guard trips, the conversation pauses with the reason **AI paused - reply loop detected**, and a row in the timeline marks the moment. The AI sends nothing more. Check the thread and reply yourself if a person is on the other end. To hand the conversation back to the AI, mark it read. There is nothing to configure. The guard is always on. ## Configuring pause behavior Some pause reasons are configured per agent, in the agent's **Settings** tab: * Protection keywords that trigger a pause * Profanity detection * Message limits * Whether to pause after a completed booking Three pauses are not configurable. The [reply loop](#reply-loops) guard is always on. Pausing after a **teammate's reply** always happens, on every channel. Pausing after an **escalation** always happens too: the AI stays out of an escalated conversation until it closes, with no per-agent setting to turn that off. See [Chat Agents](/agents/voice-agents#chat-agents) for full configuration details. # Channels Source: https://docs.nedzo.ai/unibox/channels Learn how voice, SMS, email, Instagram DM, Messenger, and web agent channels work in Unibox. Each channel has its own capabilities. Unibox brings every customer channel into one timeline. Here's how each channel works. ## Voice calls Every inbound and outbound call is logged in Unibox automatically. **What you see:** * Call recording with audio player and waveform * Full transcript with speaker labels * Call metadata: direction, duration, who handled it You can play back the recording and read the full transcript directly in the timeline. Click the call to expand details. ## SMS SMS conversations appear as chat bubbles in the timeline. **Sending from Unibox:** * Pick which workspace phone number to send from * Character counter tracks message length and segment count * 160 characters per segment (GSM-7) or 70 characters (Unicode/emoji) * Max 1,600 characters per message **Compliance:** First-time messages to a contact include an opt-out footer automatically. ### Opt-out handling (TCPA) Nedzo enforces SMS opt-outs per `(contact, phone number)`. A contact opted out from one of your numbers is still reachable from another, but every outbound path — Unibox composer, the API, workflows, agent auto-replies, and bulk sends — is blocked once they've opted out from a given number. **Inbound keywords that opt a contact out:** `STOP`, `STOPALL`, `UNSUBSCRIBE`, `CANCEL`, `END`, `QUIT` **Inbound keywords that re-opt a contact in:** `START`, `UNSTOP`, `YES` Keyword matching is case-insensitive and ignores leading/trailing whitespace and punctuation. Carrier-level opt-outs (delivered via Telnyx webhooks) are honored the same way. **In Unibox:** * The contact panel shows an **Opted out** badge on the contact's phone number when an opt-out is on file. Hover for the source (`keyword`, `carrier`, or `manual`) and the date. * The conversation list shows the same badge so operators can spot opted-out contacts before opening the thread. * The composer's send button is disabled with an explanation when the selected workspace number is blocked for that contact. **Manual override:** Click the badge in the contact panel to opt the contact back in manually (records source `manual`). Use this only when you have explicit consent — opting a contact back in without their consent violates TCPA. ## Email Email messages display as cards with From, To, and Subject headers. **Sending from Unibox:** * Add one or more recipients in the "To:" field * Subject is auto-filled as a reply to the existing thread * Larger compose area for longer messages * The sender you see depends on which setup routed the email in — see below There are two ways email reaches Unibox, depending on how your workspace is set up: **Email channel (plans on Nedzo's current pricing)** — Turning the channel on needs a current plan, but sending on a channel that is already provisioned does not. Forward your support inbox to your workspace's intake address and conversations land in Unibox automatically, with the true original sender captured as the contact. Replies send from your workspace address by default, or from your own authenticated domain. Ned can auto-reply on these conversations like any other channel. Set up from **Settings > Channels > Email** — see [Email channel](/integrations/email-channel). **Per-agent inbound addresses (custom sending domain)** — Inbound emails are routed to a chat agent by matching the recipient address against that agent's own inbound address (`chat_agents.inbound_email_local_part`), and replies on those threads come from the agent's inbound address so the customer sees a consistent sender across the whole thread. Requires a verified email domain in **Settings > Integrations > Email** — see [Chat agents → Email channel](/agents/voice-agents#email-channel) for setup. ## Instagram Instagram DMs sync to Unibox when you connect your Instagram Business or Creator account. **What you see:** * Contact's Instagram username * Full DM history **Replying:** Instagram enforces a **24-hour reply window**. You can only respond within 24 hours of the customer's last message. If the window expires, the send button is disabled. **Requires:** A connected Facebook Page with a linked Instagram account in **Settings > Integrations > Instagram**. ## Messenger Facebook Messenger conversations sync to Unibox when you connect a Facebook Page. **What you see:** * Contact's Messenger name * Facebook Page the conversation is on * Full message history **Requires:** A connected Facebook Page in **Settings > Integrations > Instagram** (Instagram and Messenger share the same Facebook connection). ## Web Agent Web Agent conversations appear in Unibox when visitors interact with a chat or voice widget embedded on your website. **What you see:** * Chat messages between the visitor and your AI agent * The URL of the page the visitor was on when the conversation started * Contact name (automatically extracted from the conversation) * The agent's name next to the channel, for example *Chat · Website Agent*, so you can tell your widgets apart ### Email follow-up for unanswered replies Visitors often close the widget before a reply comes in. Nedzo emails them what they missed. * **When it sends:** 10 minutes after the last reply in the chat, if the visitor hasn't written since. Each new reply restarts the 10 minutes. * **What it holds:** every reply since the visitor's last message, from the AI agent or your team, in one email. * **How often:** once per burst of replies. The next email can only go out after the visitor writes again. * **Replies thread back.** When the visitor answers the email, the answer lands in the same conversation. * **No extra charge.** The replies were already counted in the chat, so the email isn't billed again. It only sends when the visitor's email is on the contact and your workspace has the [Email channel](/integrations/email-channel) set up. The email shows in the thread, so your team sees what went out. **Requires:** A web agent configured and embedded on your site. See [Web Agent](/agents/voice-agents#web-agent) for setup. ## Attachments & media Email, Instagram, Messenger, and WhatsApp conversations support images, video, and audio in both directions. **Receiving:** Incoming images, videos, and audio are stored and rendered inline in the timeline — images and video play in place, audio gets a player, and other files appear as download links. **Sending:** Use the paperclip in the composer to attach one or more files, preview them as chips, and send (with or without accompanying text). Attachments up to 16 MB are supported. **SMS** does not support media attachments — the composer has no paperclip on SMS, and attachments sent to an SMS thread are rejected. Instagram, Messenger, and WhatsApp still enforce their own reply windows (for example, Instagram's 24-hour window). Media follows the same rules as text — if the window is closed, you can't send. ## Unified timeline When a contact has conversations across multiple channels, everything merges into one chronological timeline. A voice call from Monday, an SMS on Tuesday, and an Instagram DM on Wednesday all appear in order under the same contact. You pick which channel to reply through using the mode dropdown in the composer — the timeline always shows everything together. # Conversations Source: https://docs.nedzo.ai/unibox/conversations View, filter, search, snooze, tag and bulk-manage customer conversations in Unibox. Conversations are grouped by contact across every channel. All conversations are grouped by contact. If a contact has a voice call, an SMS thread, and an Instagram DM, they all appear under one entry. ## Conversation list The left column shows all your conversations with: * **Contact name** (bold if unread) * **Channel and agent**, for example *Chat · Website Agent*. On email it comes after the subject. The thread header shows the same line. * **Latest message preview** * **Timestamp** (relative — 5m, 2h, 1d) * **Blue dot** for unread conversations * **Unread count badge** ### When the blue dot clears The dot means the contact sent something you have not read yet. * It clears once you open the conversation and the new messages scroll into view. Opening a conversation and never scrolling down to the new messages leaves it unread. * It comes back when the contact sends another message. * A message arriving while you already have that conversation open on screen does not bring it back. You are looking at it. * Replies from your agent or system messages never turn it on. Only messages from the contact do. Reading a conversation does not count as activity, so clearing the dot never bumps a conversation to the top of the list or changes its timestamp. ### List order The list is sorted by **last message**, newest at the top. The timestamp on each row is the time of that last message. What counts as a message: * **Chat channels** (SMS, email, WhatsApp, Instagram, Messenger, web chat): the newest message from the contact, your agent, or a teammate. * **Calls**: the time the call started. * A conversation with no messages yet sorts by the time it was created. Anything else you do to a conversation leaves its place in the list alone. Opening it, reading it, assigning it, handing it back to the AI, snoozing, or marking it read or unread does not move it to the top. System events, such as the AI pausing or a conversation reopening, do not count as messages either. ## Filtering ### Tabs Four lenses sit in a tab row above the list: * **All** — every conversation in the workspace * **Mine** — conversations assigned to you * **Unread** — conversations nobody has read yet * **Snoozed** — conversations you've deferred, with the time each one wakes Tabs are lenses, not access restrictions — **All** shows everyone's conversations. ### Unread and Read A conversation is **Unread** until someone on your team reads it. Then it is **Read** and leaves the **Unread** tab. * Opening a conversation marks it read once its newest messages are on screen, the same moment the [blue dot](#when-the-blue-dot-clears) clears. * A new message from the contact puts it back in **Unread**. A callback from the contact lands there too. * Conversations you start are read from the start. That covers calls from workflows and campaigns, and the first SMS, email or WhatsApp message your team or a workflow sends. They stay out of **Unread** until the contact replies. * A conversation that has ended, or that is snoozed, is never in **Unread**. * **Mark as unread** puts a conversation back in **Unread**. ### Tab counts The badges on **Mine**, **Unread** and **Snoozed**, and the count next to the Unibox title, count rows. One contact is one row, so the count matches the rows you see in that tab. * A contact with two unread conversations counts as **1** on **Unread**. * A visitor with no contact record shows one row per conversation, so a visitor with two unread web chats counts as **2**. * **Mine** counts your contacts with at least one open or snoozed conversation assigned to you. A contact whose conversations have all ended counts as **0**. * The count next to the Unibox title is the same number as **Unread**. * Counts ignore the filter popover and search. They always show the whole tab. * **All** has no count. **Mine** shows conversations assigned to you. Unassigned inbound (email arrives with no assignee) appears under **All** until someone claims it. ### Filter popover Open the filter popover for finer control: * **Channel** — a tree covering Call, SMS, Email, WhatsApp, Instagram, Messenger and Web Chat * **Status** — Unread, Snoozed, or Read * **Assigned to** — a specific teammate, or unassigned * **Created** — a date range **Reset** clears every filter; **Apply** commits them. ### Search Search by contact name, message content, or phone number. Search runs server-side across your full history, including custom field values, and results update as you type. ### Shareable filters Your tab, search term and filters are all kept in the address bar, so the URL you're looking at reproduces the same view for a teammate. ## Managing conversations ### Marking conversations read or unread Click **Mark as read** in the thread header when you're finished with a conversation. It leaves **Unread** and the conversation ends, so the contact's next message starts a new one. The same button reads **Mark as unread** on a read conversation, and puts it back in **Unread**. Both are also in the **⋯** menu. **Status** is where the conversation sits in your queue, and you control it: Unread, Snoozed or Read. The status pill in the thread header appears only for **Read** and **Snoozed** conversations. An unread conversation is the normal case and needs no label. ### Snoozing conversations Snooze a conversation to hide it until you actually want it back. Pick a preset or a custom time: | Preset | Wakes | | - | - | | Later today | 3 hours from now | | Tomorrow | 9:00 tomorrow | | Next week | 9:00 next Monday | A snoozed conversation returns on its own at the wake time. It also wakes early if the customer replies in the meantime, so a snooze can never hide a waiting customer. ### Tags Add tags from the **Tags** card in the details panel, or to several conversations at once from the bulk console. Tags are workspace-wide, so the same label means the same thing to everyone. ### Bulk actions Tick the checkbox on any row to enter multi-select. A selection bar takes the place of the tabs. It shows how many rows you picked, its own checkbox ticks every row loaded on screen, and it opens the bulk console, where one action applies to the whole selection: * Assign to a teammate * Change status — Unread, Snoozed (with a wake time) or Read * Add or remove tags Selections can mix voice, chat and web conversations — a single request applies the change to all of them. #### Select all in a tab On **Mine**, **Unread** and **Snoozed**, the selection bar offers **Select all N in Unread** (or Mine, or Snoozed). It selects every row in that tab, including rows you haven't scrolled to yet. Up to 2,000 conversations can be selected this way. * **N** is the tab count, so it counts rows, not conversations. * An action on a selected row applies to every conversation of that contact. * It is hidden on **All**, while a search is active, and while a channel or created-date filter is on. In those cases the number would not match the rows on screen. * A row leaves the selection once the action changes it. A row whose conversations all failed stays selected so you can try again. ### Blocking a sender There are two rules for an unwanted sender. They are easy to confuse, and only one of them keeps the mail: | Rule | Messages from the sender | The AI | | - | - | - | | **Block** | Discarded before they reach Unibox. They cannot be recovered. | Never sees them | | **No AI reply** | Still arrive in Unibox as normal | Does not answer them. Your team can still reply. | Use **No AI reply** for senders you want to keep but not have the AI answer, such as automated notifications or a vendor's system mail. From a thread, open the **⋯** menu. When the sender has no rule yet, it offers **Block sender** and **No AI reply**. Once a rule exists, the same spot lifts it: **Unblock sender** for a block, **Let the AI reply again** for a No AI reply rule. On voice conversations only **Block sender** is offered, because a call is answered as it arrives and there is no later reply to hold back. A thread under a No AI reply rule shows a band under the header: *The AI does not reply to this sender. Messages still arrive here.* The band reads the live rule, so it goes away on every affected thread as soon as the rule is removed. Manage all rules under **Settings → Workspace → Blocklist**. Pick the channel tab (Email, SMS, WhatsApp, Voice, Instagram or Messenger), type the sender, and press **Block** or **No AI reply**. Each rule applies to the channel it was added on, for everyone in the workspace. Every listed rule carries a badge naming which of the two it is. * **Email** rules can match a single address or a whole domain. A domain rule also covers its subdomains, so `example.com` covers `mail.example.com`. When a domain rule is what matches a thread, the thread menu shows **Blocked by domain rule** or **No AI reply, by domain rule** and opens the blocklist, since lifting it affects every sender on that domain. * **Voice** has only **Block**. * Because there are two actions, pressing Enter does not add a rule. Press the button for the rule you mean. On Voice, where there is one action, Enter still adds the block. Only members who can manage Unibox see these menu options. ### Downloading a transcript The thread overflow menu offers **Download transcript**. Message threads download as CSV; voice and web-chat transcripts download as plain text. ### Real-time updates New messages and calls appear instantly — no need to refresh. Unibox uses real-time subscriptions to keep everything up to date. ### Sharing a conversation link Selecting a conversation updates the URL to include the contact ID: ``` https://app.nedzo.ai/unibox?contact= ``` This URL is shareable. Pasting it into the browser (or a Slack message, ticket, etc.) opens Unibox with that contact's timeline already selected. If the conversation has moved out of the current view (for example, you selected the **Unread** tab but the conversation has since been read), Unibox still loads it via the deep link. Removing the query parameter or clicking another conversation updates the URL accordingly. Links shared before this change used `?conversation=`. Those still work — Unibox opens the contact's full timeline and updates the address bar to the `?contact=` form. Links in notification emails may still arrive in the older format. ## Timeline The center column shows the full conversation history for a contact, merged across all channels in chronological order. ### Voice calls Each call shows: * **Audio player** with waveform — play, pause, and scrub through the recording * **Call details** (expandable): sentiment, direction, duration, who handled it, and date * **Full transcript** (expandable) — every line of the conversation, labeled by speaker ### Messages SMS, email, WhatsApp, Instagram, and Messenger messages appear as chat bubbles: * **Outbound messages** — right-aligned, blue background * **Inbound messages** — left-aligned, gray background * **AI responses** — marked with an "AI" badge * **Team member messages** — show the sender's name * **WhatsApp app replies** — replies your team sends from the WhatsApp Business app on a phone show a smartphone icon and the label **Sent from WhatsApp app**. They are never shown as sent by the AI. WhatsApp does not say which person sent them, so no name is shown. See [How Coexistence works](/integrations/whatsapp#how-coexistence-works). * **Email messages** — card layout with From, To, and Subject headers ### Delivery status Each outbound message shows its delivery status: | Icon | Status | | - | - | | Spinner | Sending | | Single check | Sent | | Double check (blue) | Delivered | | Double check (pink) | Read | | Red X | Failed | ## Replying Send messages directly from Unibox using the composer at the bottom of the timeline. ### Reply or Note The composer is a single pill with a **Reply / Note** toggle. A reply goes to the customer; a note is internal and only your team sees it. ### Tagging a teammate in a note Type `@` in a note to open a list of your workspace teammates. Keep typing to filter it by name or email, use the arrow keys to move through it, and press Enter or click to insert the tag where your cursor is. Escape closes the list without tagging anyone. You can tag several people in one note. The **Notify teammate** button does the same thing. Everyone you tag gets an email with the note, who wrote it, and a link back to the conversation. You never get an email for tagging yourself. You can only tag people who are members of the workspace. Removing a name from the note before you save it also removes the tag, so nobody is emailed. ### Switching channels The channel pill opens a dropdown of every channel available for that contact — SMS, Email, WhatsApp, Instagram, Messenger, Web Chat or Voice. The composer changes to match: an email reply gets recipient and subject fields, an SMS reply gets a segment counter, and a voice reply offers a call back. Replying to a web-chat conversation uses the **Chat** channel and is delivered to the visitor's chat widget. It is not sent as a text message. ### Draft with Ned **Draft with Ned** in the composer toolbar writes a first draft of your reply from the conversation so far. It fills the composer — nothing is sent until you review and send it yourself. ### Markdown Formatting you write is rendered for the recipient rather than sent as raw symbols, on both email and Slack. ### SMS * Select which phone number to send from * Character counter shows segment count (160 characters per segment) * Max 1,600 characters ### Email * Add recipients in the "To:" field * Supports multiple recipients * Subject auto-filled as a reply to the original thread #### Reply-all (Cc) When an inbound email arrives with other people copied, the composer shows a **reply-all** toggle next to the recipients. Without it a reply only ever reaches the one person who sent the message, so anyone else on the thread never sees the answer. * The toggle only appears when the thread actually carries other participants. On a two-person thread there is nothing to show. * Turning it on copies in the thread's other participants. Each appears as a chip — remove any one with the **✕** on the chip if you don't want them copied. * Replies default to **sender-only**. The toggle resets after every send, and switching conversations clears it, so a Cc list can never carry from one thread to the next. * Up to **25** Cc recipients per reply. Some addresses are dropped before sending, and the reply still goes out to everyone else. When that happens you get a notice naming who was left off — `Reply sent, but not copied to …`. An address is dropped when it is: * **malformed** — not a valid email address * **one of your own channel addresses** — your workspace's inbound address is never copied back onto its own thread * **suppressed** — the address previously bounced or unsubscribed, so it stays suppressed #### Email from a web-chat conversation In a web-chat conversation you can switch the channel pill from **Chat** to **Email** to email the visitor instead. This needs the visitor's email on the contact and the [Email channel](/integrations/email-channel) set up. The composer then shows the same fields as an email reply: * **From**: the address the email sends from, same as an email thread. * **Subject**: a web chat has no subject of its own, so you set one here. * For the first email in the chat, the subject is required. Leave it blank and the send stops with *Add a subject. This is the first email in this chat.* Your draft stays in the composer. * After that, the field shows `Re:` plus the subject of the last email in the chat. Leave it blank to send that, or type a new one. * Up to **998** characters. Line breaks become spaces. * **Cc** and **Bcc**: on plans on Nedzo's current pricing, click **Cc** or **Bcc** on the From row and type an address. Press Enter or a comma to add it. Switching to another contact clears the subject you typed. If a send fails, the subject, Cc and Bcc come back so you can retry. ### Instagram Instagram only allows replies within 24 hours of the customer's last message. If the window has expired, sending is disabled with a notice. If your workspace has more than one Instagram account connected, the thread header names the account the conversation came through. See [Instagram](/integrations/instagram). ### Starting a new conversation Click **New conversation** at the top of the conversation list. Pick a contact, or type a phone number or email address, then pick the channel. For an **Email**, the **From** pill on the compose pane sets the address the email sends from. Its list starts with your workspace default, then each address on your verified domain. What the pill shows is the address the email goes out from. If you don't change it, the email uses the workspace default. ### Keyboard shortcut Press **Cmd+Enter** (Mac) or **Ctrl+Enter** (Windows) to send. ## Contact details Toggle the right column to see contact info: * **Name** — A single field for the contact's full name. The system auto-parses it: the first word is stored as the first name, and the rest as the last name. * Phone number and email * Quick actions: Call, Email * Channel-specific info (Instagram username, Facebook Page, etc.) All fields in the contact panel are editable — click any field to update it. **Custom fields** appear in their own section between Contact Info and Recent Conversations. Every custom field your workspace defines is shown — including empty ones (marked "Not set") — and each is editable inline, so agents can read and update details like a school name or account ID without leaving the conversation. # Unibox Source: https://docs.nedzo.ai/unibox/overview Manage all customer conversations across voice, SMS, email, WhatsApp, Instagram, Messenger and web chat in one unified inbox. Filter, search, snooze, tag and respond. Unibox is your unified inbox for all customer conversations — voice calls, SMS, email, WhatsApp, Instagram DMs, Messenger messages and web chat — all in one place. Every interaction across every channel shows up here, grouped by contact. No switching between tools. ## Layout Unibox is a four-column workspace: 1. **Sidebar** (far left) — the main sidebar for moving between Unibox and the rest of the platform. It stays open or collapsed the way you left it. Use its toggle to collapse it when you want more room. 2. **Conversation list** — your conversations, grouped by Today / Yesterday / Earlier, with tabs for All, Mine, Unread and Snoozed 3. **Thread** (center) — the full conversation history for the selected contact, with the composer at the bottom 4. **Details** — contact info, tags and quick actions, or call details on a voice conversation The details column is drag-resizable, and can be collapsed when you want more room for the thread. Below 1200px the layout adapts to the narrower viewport. Unibox supports both light and dark mode, following your system setting. ## What you can do View, filter, search, and manage conversations across all channels How voice calls, SMS, email, Instagram, and Messenger appear in Unibox Pause, resume, and manage AI responses in conversations # Condition action Source: https://docs.nedzo.ai/workflows/actions/condition Split your workflow into different paths using conditional logic. Evaluate contact data, call outcomes, or variables to route each execution. Split your workflow into different paths based on conditions. The workflow evaluates each path's conditions and follows the first one that matches. ## Configuration A condition node has two or more **paths**. Each path has: | Field | Required | Description | | - | - | - | | Label | Yes | A name for this path (e.g., "Interested", "No answer") | | Conditions | Yes | One or more rules to evaluate | | Default | — | One path must be marked as the default fallback | The default path runs when no other path's conditions match. ## Condition rules Each condition evaluates a field against a value using an operator. | Field | Required | Description | | - | - | - | | Field | Yes | The data field to check (contact field, trigger data, or previous step output) | | Operator | Yes | How to compare | | Value | Yes | The value to compare against | The value can hold variables, the same ones you use in actions. For example, `{{contact.email}}`, `{{custom.Plan}}`, `{{nodeId.outcome}}` or `{{trigger.webhook.body.orderId}}`. Nedzo fills them in before it compares. `{{trigger.contact.x}}` reads the same contact record as `{{contact.x}}`, so both give the same value in a condition and in an action. If a variable in the value has no data, the condition does not match. **Is empty** and **Is not empty** ignore the value. ### Available fields * **Contact fields** — `firstName`, `lastName`, `email`, `phone`, `businessName`, `dnc` * **Trigger data** — `tagName`, `channel`, `outcome`, `message`, or any webhook payload field * **Custom fields** — Any custom field on the contact * **Previous step outputs** — Reference outputs from earlier actions using dot notation (e.g., `nodeId.outcome`) #### Do Not Disturb (DND) `dnc` is the Do Not Disturb (DND) field on every contact. It is set to `true` when the contact has opted out (STOP keyword on SMS, unsubscribe link on email, a conversation with the **Do not disturb** outcome on any channel, or the DND switch in the contact panel), and while it is on, nothing is sent to the contact on any channel. Branching on DND lets you log or alert on opted-out contacts while the workflow continues down a "log only" path. | Operator | Description | Example | | - | - | - | | Equals | DND has a specific value | `dnc` equals `true` | | Not equals | DND does not have that value | `dnc` not equals `true` | **Typical pattern:** | Path | Condition | Then | | - | - | - | | DND | `dnc` equals `true` | Slack message to #compliance, end | | Default | — | Send SMS / Send Email / Voice call | ### Operators | Operator | Description | Example | | - | - | - | | Equals | Exact match | outcome equals "Follow up" | | Not equals | Does not match | outcome not equals "voicemail" | | Contains | Text includes value | message contains "pricing" | | Not contains | Text excludes value | email not contains "spam" | | Greater than | Numeric comparison | durationSeconds greater than 60 | | Less than | Numeric comparison | durationSeconds less than 10 | | Starts with | Text begins with | phone starts with "+1" | | Ends with | Text ends with | email ends with "@gmail.com" | | Is empty | Field has no value | email is empty | | Is not empty | Field has a value | phone is not empty | #### Date operators Date-typed custom fields (see [Contacts → Custom fields](/concepts/contacts#field-types)) support a separate operator set that compares calendar dates rather than string values. | Operator | Description | Example | | - | - | - | | Before | The field's date is earlier than the value | renewalDate before `2026-06-01` | | After | The field's date is later than the value | renewalDate after `2026-01-01` | | Equals | The field's date is exactly the value | birthday equals `2026-05-02` | | In Between | The field's date is within an inclusive range | renewalDate in between `2026-04-01` and `2026-04-30` | Dates are compared in `YYYY-MM-DD` form. The same operators are also available in the Contacts page filter bar. ## Example **Route by call outcome:** Trigger: Conversation ended → Agent Type: voice | Path | Condition | Then | | - | - | - | | Interested | outcome equals "Follow up" | Send follow-up email | | Voicemail | outcome equals "voicemail" | Wait 4 hours → Retry call | | No answer | outcome equals "no\_answer" | Send SMS | | Default | — | Add "needs-review" tag | **Route by channel:** Trigger: Contact replied | Path | Condition | Then | | - | - | - | | SMS reply | channel equals "sms" | Slack message to #sms-replies | | Instagram DM | channel equals "instagram" | Slack message to #social | | Default | — | Slack message to #general | # Workflow actions Source: https://docs.nedzo.ai/workflows/actions/overview Explore the actions available in Nedzo workflows, including voice calls, SMS, email, Slack messages, webhooks, contact updates, conditions, and wait steps. Actions are the steps in your workflow that do something — make a call, send a message, update a contact, or call an external API. Add as many actions as you need, and they execute in order. ## Variables Use `{{variable}}` syntax in any text field across all actions. | Variable | Description | | - | - | | `{{firstName}}` | Contact's first name | | `{{lastName}}` | Contact's last name | | `{{email}}` | Contact's email | | `{{phone}}` | Contact's phone number | | `{{businessName}}` | Contact's company name | Trigger data and previous step outputs are also available. For example, `{{nodeId.status}}` references the output of a previous action. ## Retries All actions retry up to 3 times with exponential backoff on transient failures (network timeouts, rate limits). Permanent failures (invalid config, auth errors) fail immediately. # Send email action Source: https://docs.nedzo.ai/workflows/actions/send-email Send an email from a custom domain or the default Nedzo domain as a workflow step. Supports dynamic variables, HTML, and reply tracking. Send an email as part of your workflow. Emails are sent from your custom domain or the default `nedzo-mail.com` domain. Replies are tracked in Unibox. ## Configuration | Field | Required | Default | Description | | - | - | - | - | | Recipient type | Yes | Contact | **Contact** sends to the trigger contact's email. **Custom** lets you enter a specific address. | | Custom email | If custom | — | Email address. Only shown when recipient type is "Custom". | | From name | No | Workspace default | The sender name that appears in the recipient's inbox. | | Subject | Yes | — | Email subject line. Supports `{{variables}}`. Max 998 characters. | | Body | Yes (Fixed mode) | — | Email content. Supports `{{variables}}`. Max 25,600 characters. Only used in Fixed mode. | | Instruction | No (AI Generated mode) | — | Instructions for the AI when composing the email. Only shown in AI Generated mode. | ### Body mode The Send Email action supports two modes for composing the email body: | Mode | Description | | - | - | | **Fixed** | You write the exact email content. Supports `{{variables}}` for personalization. | | **AI Generated** | The AI composes the email body based on conversation context and your instructions. | **Fixed mode** is the default. You write the full email body in the Body field with variable support. **AI Generated mode** lets the AI write the email. The AI uses the conversation context (call transcript, previous steps, contact data) to compose a relevant email. Add an **Instruction** to guide what the AI should write — for example, *"Summarize the key points discussed in the call and include next steps"* or *"Write a follow-up thanking them for their time"*. When using AI Generated mode, the Instruction field is optional but recommended. Without it, the AI composes the email based on conversation context alone. With it, you get more control over tone, content, and structure. ## Variables Personalize the subject and body with variables: ``` Subject: Thanks for your time, {{firstName}} Body: Hi {{firstName}}, Thanks for chatting with us today. As discussed, I'm sending over the details for {{businessName}}. Let me know if you have any questions. ``` Available variables: `{{firstName}}`, `{{lastName}}`, `{{email}}`, `{{phone}}`, `{{businessName}}`, plus any trigger data or previous step outputs. ## Custom domain If you've set up a custom email domain in **Settings > Integrations > Email**, emails are sent from that domain. The domain must be DNS-verified before emails can be sent. Without a custom domain, emails are sent from `nedzo-mail.com`. Setting up your own domain improves deliverability and brand recognition. ## Reply tracking When a contact replies to a workflow email, the reply appears in Unibox under that contact's conversation. An inbound webhook route is automatically configured for your domain. ## Example **Send a summary after a call:** 1. Trigger: Conversation ended → Agent Type: voice 2. Condition: Outcome equals "Follow up" 3. Action: Send email * Recipient: Contact * From name: Your Company * Subject: *"Summary of our conversation"* * Body: *"Hi `{{firstName}}`, thanks for taking the time to speak with us. Here's a quick recap of what we discussed..."* # Send SMS action Source: https://docs.nedzo.ai/workflows/actions/send-sms Send a text message to a contact or custom phone number as a workflow step. Messages appear in Unibox alongside other conversations for unified tracking. Send an SMS message as part of your workflow. Messages appear in Unibox alongside other conversations. ## Configuration | Field | Required | Default | Description | | - | - | - | - | | Phone number | Yes | — | Which workspace phone number to send from. Select from your purchased numbers. | | Recipient type | Yes | Contact | **Contact** sends to the trigger contact's phone. **Custom** lets you enter a specific number. | | Custom phone | If custom | — | Phone number in E.164 format (e.g., `+14155551234`). Only shown when recipient type is "Custom". | | Message | Yes | — | The message text. Supports `{{variables}}`. Max 1,600 characters. | ## Variables Use variables to personalize the message: ``` Hi {{firstName}}, thanks for chatting with us today. We'll send over the details to {{email}} shortly. ``` Available variables: `{{firstName}}`, `{{lastName}}`, `{{email}}`, `{{phone}}`, `{{businessName}}`, plus any trigger data or previous step outputs. ## Compliance The first SMS sent to a new phone number automatically includes an opt-out footer with your workspace info and instructions to reply STOP. This is required for compliance and happens automatically — you don't need to add it to your message. Two independent toggles control this, under **Phone Numbers → Compliance**: the company/sender name and the opt-out notice. Both are **on by default**. You can disable either one per workspace (for example, if your carrier already appends opt-out language), but leaving them on is strongly recommended — opt-out text is generally an A2P/carrier requirement and turning it off makes compliance your responsibility. ### Opt-out enforcement If the contact replied `STOP` (or any of `STOPALL`, `UNSUBSCRIBE`, `CANCEL`, `END`, `QUIT`) to the workspace number you're sending from, the action fails with an opt-out error and the SMS is not sent. The check is per `(contact, phone number)` — a contact opted out from one number can still be reached from another. A contact who later replies `START`, `UNSTOP`, or `YES` is opted back in automatically and the workflow can send again. Carrier-level opt-outs (received via Telnyx webhooks) are enforced the same way. See [Unibox → SMS opt-out handling](/unibox/channels#opt-out-handling-tcpa) for full opt-out behavior. ## Limits | Limit | Value | | - | - | | Max message length | 1,600 characters | | Daily SMS per workspace | 100 | | Segment length | 160 chars (GSM-7) or 70 chars (Unicode/emoji) | Messages longer than one segment are sent as multi-part SMS and count as multiple segments. ## Checks Before sending, the action verifies: * The contact has a phone number * The contact does not have Do Not Disturb (DND) on * The contact has not opted out from the selected workspace number * The phone number is active in your workspace * The daily SMS limit hasn't been reached If any check fails, the action fails with a descriptive error. ## Example **Follow-up after a call:** 1. Trigger: Conversation ended → Agent Type: voice 2. Condition: Outcome equals "Follow up" 3. Action: Send SMS * Phone number: Your workspace number * Recipient: Contact * Message: *"Hi `{{firstName}}`, great speaking with you! As discussed, here's the link to get started: [https://example.com/signup](https://example.com/signup)"* # Send webhook action Source: https://docs.nedzo.ai/workflows/actions/send-webhook Send an HTTP request to any external URL from your workflow. Connect Nedzo to CRMs, analytics platforms, automation tools, or custom backends via webhooks. Send an HTTP request to any URL. Use this to connect Nedzo to services that don't have a native integration — CRMs, analytics tools, automation platforms, or your own backend. ## Configuration | Field | Required | Default | Description | | - | - | - | - | | URL | Yes | — | The endpoint to send the request to. Supports `{{variables}}`. | | Method | Yes | POST | HTTP method: GET, POST, PUT, PATCH, or DELETE. | | Headers | No | — | Custom headers as key-value pairs. Supports `{{variables}}` in values. | | Body | No | — | Request body for POST, PUT, PATCH, and DELETE. Supports `{{variables}}`. | ## Variables Use variables in the URL, headers, and body: ```json theme={null} { "contact_name": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "email": "{{email}}", "call_outcome": "{{outcome}}" } ``` When the body is valid JSON, variables are substituted in a JSON-safe way (special characters are properly escaped). ## Output | Field | Description | | - | - | | `statusCode` | The HTTP response status code | | `body` | The response body (max 1 MB) | | `headers` | Response headers | The response data is available to subsequent steps. For example, if the webhook returns `{"orderId": "123"}`, you can reference `{{nodeId.body.orderId}}` in later actions. ## Example: Send call data to a CRM ```json theme={null} { "method": "POST", "url": "https://your-crm.com/api/calls", "headers": { "Authorization": "Bearer your-api-key", "Content-Type": "application/json" }, "body": { "contact_name": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "email": "{{email}}", "outcome": "{{outcome}}" } } ``` ## Common use cases * **CRM updates** — Push call outcomes to Salesforce, HubSpot, or any CRM with an API * **Zapier / Make** — Trigger Zapier Zaps or Make scenarios via webhook URL * **n8n workflows** — Trigger n8n automations from Nedzo * **Custom logging** — Send conversation data to your own analytics backend * **Slack alternatives** — Post to any service that accepts incoming webhooks ## Limits | Limit | Value | | - | - | | Request timeout | 30 seconds | | Max response size | 1 MB | | Retries on failure | 3 (exponential backoff) | 5xx, 408, and 429 responses are automatically retried. 4xx responses fail immediately. ## Security * Only `http://` and `https://` URLs are allowed * Requests to private/internal IP addresses are blocked (localhost, 10.x.x.x, 172.16.x.x, 192.168.x.x, etc.) * Always use HTTPS for endpoints that require authentication # Slack message action Source: https://docs.nedzo.ai/workflows/actions/slack-message Send a message to a Slack channel from your workflow. Notify your team about call outcomes, new leads, workflow events, or any data using dynamic variables. Send a message to a Slack channel as part of your workflow. Use this to notify your team about call outcomes, new leads, or any workflow event. ## Configuration | Field | Required | Description | | - | - | - | | Channel | Yes | The Slack channel to post to. Select from a dropdown of available channels. | | Message | Yes | The message text. Supports `{{variables}}`. Max 2,000 characters. | ## Variables Include contact and workflow data in your message: ``` New conversation completed with {{firstName}} {{lastName}} ({{phone}}). Outcome: {{outcome}} ``` Available variables: `{{firstName}}`, `{{lastName}}`, `{{email}}`, `{{phone}}`, `{{businessName}}`, plus any trigger data or previous step outputs. ## Channel access * **Public channels** — Nedzo automatically joins the channel when posting. * **Private channels** — You need to manually invite the Nedzo app to the channel using `/invite` in Slack before it can post. ## Token refresh Slack tokens expire periodically. Nedzo refreshes them automatically. If a refresh fails, the action fails and the Slack integration shows a **Reconnect** prompt in your dashboard. ## Requires Slack must be connected in **Settings > Integrations > Slack**. ## Example **Notify sales on hot lead:** 1. Trigger: Contact tag added → "hot-lead" 2. Action: Slack message * Channel: #sales * Message: *"🔥 Hot lead: `{{firstName}}` `{{lastName}}` (`{{phone}}`) just tagged as hot lead. Follow up ASAP."* **Post call summary:** 1. Trigger: Conversation ended → Agent Type: voice 2. Action: Slack message * Channel: #call-updates * Message: *"Call with `{{firstName}}` `{{lastName}}` — `{{outcome}}`. Duration: `{{durationSeconds}}`s."* # Update contact action Source: https://docs.nedzo.ai/workflows/actions/update-contact Create or update a contact record within a workflow. Match contacts by phone or email, then set fields like name, tags, and custom metadata automatically. Create a new contact or update an existing one. The action matches contacts by phone number or email — if a match is found, it updates the record. If not, it creates a new contact. ## Configuration | Field | Required | Description | | - | - | - | | First name | No | Contact's first name. Supports `{{variables}}`. | | Last name | No | Contact's last name. Supports `{{variables}}`. | | Phone | No | Phone number in E.164 format (e.g., `+14155551234`). | | Email | No | Email address. Automatically lowercased. | | Business name | No | Company name. Supports `{{variables}}`. | | Add Tags | No | Tags to add to the contact. Tags already on the contact stay. Tags not listed here are untouched. | | Remove Tags | No | Tags to remove from the contact. Tags not listed here are untouched. | | Custom fields | No | Additional fields to set as key-value pairs. A yes/no custom field shows a **True** / **False** picker. | At least one identifying field (phone or email) should be provided so the action can match or create the contact. ## How matching works 1. If a **phone number** is provided, the action searches for an existing contact with that number 2. If no phone match, it searches by **email** 3. If a match is found, the contact is **updated** with the new data 4. If no match, a new contact is **created** Fields you leave blank are not overwritten on existing contacts. ## Output | Field | Description | | - | - | | `contactId` | The contact's ID (new or existing) | | `created` | `true` if a new contact was created | | `updated` | `true` if an existing contact was updated | ## Example **Tag interested contacts after a call:** 1. Trigger: Conversation ended → Agent Type: voice 2. Condition: Outcome equals "Follow up" 3. Action: Update contact * Add Tags: "called", "interested" **Create contact from webhook data:** 1. Trigger: Webhook (receives form submission data) 2. Action: Update contact * First name: `{{firstName}}` * Last name: `{{lastName}}` * Email: `{{email}}` * Phone: `{{phone}}` * Add Tags: "website-lead" 3. Action: Voice call with sales agent **Move a contact between segments:** 1. Trigger: Booking confirmed 2. Action: Update contact * Add Tags: "customer" * Remove Tags: "lead", "interested" # Voice call action Source: https://docs.nedzo.ai/workflows/actions/voice-call Make an outbound AI voice call as a workflow step. Select an agent, configure caller ID and retry settings, and let the agent handle the conversation for you. Make an outbound phone call using one of your AI voice agents. The agent handles the conversation automatically based on its prompt and configuration. ## Configuration | Field | Required | Default | Description | | - | - | - | - | | Agent | Yes | — | Which voice agent places the call. Any active voice agent can be picked — every agent can both answer and dial. | | No-answer behavior | No | None | What happens when the contact doesn't pick up: **None** (stop), **Retry** (call again), or **Continue** (move to next step). | | Voicemail behavior | No | None | What happens when the call goes to voicemail: **None**, **Retry**, or **Continue**. | | Max follow-ups | No | 0 | How many times to call again on no-answer or voicemail. Up to 15 follow-ups, so a contact can get up to 16 calls counting the first. | | Follow-up delay | No | — | Wait time between retries. Set a number and unit (hours or days). | | Calling days | No | All days | Restrict calls to specific days of the week (Mon–Sun). | | Calling hours | No | All hours | Time window for calls, e.g., 9 AM – 8 PM. Uses the contact's timezone. | ## How follow-ups work When a call results in no-answer or voicemail, the configured behavior determines what happens next: * **None** — The workflow stops at this step. No retry, no next step. * **Retry** — The workflow schedules another call after the follow-up delay. This repeats up to the max follow-ups limit. * **Continue** — The workflow skips the retry and moves to the next action in the flow. Follow-ups respect calling days and hours. If a retry is scheduled outside the calling window, it's deferred to the next available time. ## Calling window and contact timezone Calling days and hours are evaluated in the **contact's** timezone, not your workspace timezone — so a 9 AM – 8 PM window means 9 AM – 8 PM where the contact is. Nedzo uses the contact's **Timezone** field first, then the contact's phone number. If it can't determine one, the call is **not placed**. It is held rather than dialed against a UTC guess, which is what previously produced calls in the middle of the night. Some contacts carry your workspace's timezone only because their phone number did not show where they are. Australian mobiles are a common case. Nedzo does not trust that zone for calls. It checks the phone number again, and if the number still gives no zone, the call is not placed. On the contact panel these contacts show a warning under **Timezone**: *Not confirmed*. To fix a held contact, open it in **Contacts** and choose its timezone, even if it is the same one, or correct the phone number. See [Contact timezone](/concepts/contacts#contact-timezone). ## Output After the call completes, the following data is available to subsequent steps: | Field | Description | | - | - | | `callId` | The call's unique ID | | `status` | Call status | | `outcome` | How the call ended. Shown as **Outcome** in the workflow builder. Holds either a call result (for example `no_answer`, `voicemail`, `failed`, `appointment_booked`) or, when the call had a conversation to classify, its [Outcome](/agents/call-analysis#outcome) (for example Appointment booked, Follow up, Not interested) | | `dataExtracted` | All [extraction fields](/agents/call-analysis#data-extraction) configured on the agent (e.g., budget, timeline) | | `recordingUrl` | URL to the call recording | `dataExtracted` requires extraction fields to be configured on the agent's **Settings > Conversation Analysis** tab. The **Outcome** field was previously labelled **Disposition** in the workflow builder. Only the label changed — the stored field is still `outcome`, so existing conditions and any saved `{{steps..outcome}}` reference keep working. When you build a condition on Outcome, the value picker offers both vocabularies: **Classified by AI** (the fixed [Outcome](/agents/call-analysis#outcome) values) and **Call result** (the fixed set above). Which one a given call carries depends on whether a conversation took place: a call with a transcript carries its AI-assigned Outcome, and a call with no transcript (for example, one that was never answered) carries a call result. ## Examples **Outreach with follow-up:** 1. Action: Voice call with sales agent * No-answer: Retry after 4 hours * Voicemail: Continue to next step * Max follow-ups: 3 * Calling hours: 9 AM – 6 PM * Calling days: Mon–Fri 2. Action: Send SMS — *"Hi `{{firstName}}`, I tried to reach you. Let me know a good time to chat."* ## Timeout Voice calls have a 5-minute timeout by default. If the call hasn't connected within that time, it's marked as failed. If the call is answered but the agent can't start, the step does not wait for the timeout. It moves on right away with the outcome `failed`. Once the call connects, it runs until the conversation ends or until the agent's **Max call duration** is reached, whichever comes first. The cap covers the whole call, including time after a transfer. See [How Max call duration works](/agents/voice-agents#how-max-call-duration-works). # Wait action Source: https://docs.nedzo.ai/workflows/actions/wait Pause a workflow for a fixed duration or until a specific date and time. Use delays to space out follow-ups, throttle messages, or schedule future actions. Pause the workflow before continuing to the next step. Use delays to time your follow-ups, avoid sending messages too quickly, or schedule actions for specific days. ## Delay types ### Duration Wait for a fixed amount of time. | Field | Required | Description | | - | - | - | | Amount | Yes | How long to wait | | Unit | Yes | Minutes, hours, or days | **Ranges:** 1 minute minimum, 60 days maximum. **Example:** Wait 2 hours after a call before sending a follow-up SMS. ### Until day Wait until a specific day of the week and time. | Field | Required | Description | | - | - | - | | Day | Yes | Monday through Sunday | | Time | Yes | Time of day (e.g., 9:00 AM) | If the specified day/time has already passed this week, it waits until next week. **Example:** Wait until Monday at 9:00 AM to start outbound calls for the week. ### Until date Wait until a specific calendar date and time. | Field | Required | Description | | - | - | - | | Date | Yes | A specific date | | Time | Yes | Time on that date | The date can be a variable, such as a custom field that holds an appointment date. Nedzo reads it when the run reaches the step, and a workflow with a variable date can be published. A date you type in must be a valid date in the future. **Example:** Wait until a product launch date before sending announcement emails. ## Timezone All delay types are timezone-aware based on your browser's timezone at the time you configure the delay. The exact resume timestamp is calculated and stored, so the workflow resumes at the correct time regardless of daylight saving changes. ## During the wait While a workflow is waiting: * The execution status shows as **Waiting** * The scheduled resume time is recorded * The workflow automatically resumes when the wait period ends ## Example **Drip sequence:** 1. Trigger: Contact created 2. Action: Send email — Welcome message 3. Action: Wait 1 day 4. Action: Send email — Getting started guide 5. Action: Wait 3 days 6. Action: Voice call with onboarding agent # Workflow executions Source: https://docs.nedzo.ai/workflows/executions Monitor, inspect, and troubleshoot workflow runs. View execution statuses, step-level details, variable values, and error logs for every workflow execution. Every time a workflow runs, it creates an execution record. Use executions to monitor what's happening and troubleshoot issues. ## History range The execution history view has a range selector to control how far back you can see runs. | Range | Availability | | - | - | | Last 24 hours | All plans | | Last 7 days | All plans | | Last 30 days | VIP plan only | On other plans the 30-day option is shown but locked behind an upgrade prompt. Existing execution records older than your range are not deleted. They just aren't surfaced in the dropdown selection. ## Needs-review digest The needs-review digest email counts failed runs that nobody has reviewed yet, over a time window. Its **Open executions** button opens **Workflows > History** and lists exactly those runs. * The heading names the window, for example *Failed runs not yet reviewed since …*, and shows the total. It matches the number in the email. * **Show all dates** drops the window and lists every failed run not yet reviewed. * **Back to all history** returns to the normal run list. The search box and range picker are hidden while this list is showing. If the date in the link is broken, the page says *The date in this link is not valid* and shows no list. ## Deleting a workflow Deleting a workflow cancels its runs that are still in progress, so no more steps run for them. ## Auto-pause and failure emails A workflow that fails **5 times in a row** switches itself off. Runs in progress stop, and admins get one email saying the workflow was auto-paused. Fix the problem, then switch the workflow back on. Some failures say nothing about the workflow, so they don't count toward the 5 or reset the count: * **The account can't send right now:** out of credits, a trial that hasn't converted, or a spending cap. * **This contact can't be reached:** DND, restricted, deleted, or no phone or email. * **A problem on Nedzo's side:** an outage, a network error, or a timeout. A workflow that is still on but fails often sends a high failure rate email. It goes out at most **once a day** per workflow. Test runs and the failures above don't count, and a workflow that is already off sends none. When Nedzo knows what an error means, both emails explain it in plain words above the raw error text. ## Execution statuses The **Status** column in **Workflows > History** shows what each run is doing. | Status | Description | | - | - | | Queued | Waiting to start. Shows how long it has waited, for example *for 12m*. | | Running | Running its steps now | | Waiting | Paused until a set time. When Nedzo knows the time, it shows the reason and when the run picks up again, for example *Outside calling window · until Oct 6, 9:00 AM*. | | Stuck | Has not moved for longer than a run should wait. Shows how long, for example *no progress for 6d*. | | Completed | Finished successfully | | Failed | An error occurred | | Cancelled | Stopped manually, or automatically because the workflow was deactivated (shown with a "Workflow deactivated" reason) | A **Waiting** run can show one of these reasons: | Reason | Why the run is paused | | - | - | | Wait step | A [Wait](/workflows/actions/wait) step is counting down | | On a call | A call is in progress. The time shown is when the call times out, not when it ends. | | Follow-up call | A follow-up call is booked for later | | Outside calling window | The call waits for your calling hours to open | | Call limit reached | The call waits until it is under the call limit again | | Rate limited | Slack asked Nedzo to slow down, so the message waits | A **Queued** run is about to start, or it is waiting behind the [concurrency cap](#concurrency-limits). The UI shows the same status for both. ### Stuck runs A run shows **Stuck** once it is past the point where Nedzo would move it on its own: * Queued for more than 4 hours * Running with no progress for 4 hours * Waiting more than 4 hours past its resume time, or 25 hours for a follow-up call If a waiting run is more than 4 hours past its resume time and nothing is set to resume it, Nedzo marks it **Failed** so it doesn't stay stuck. When runs in the list look stuck, a red banner at the top of **History** shows how many. It counts only the runs in the range and search you picked, so every run it counts is in the list below it. Runs from deleted workflows are not counted. [Contact support](mailto:support@nedzo.ai) with the run ID. ## Concurrency limits Nedzo caps how many executions run at the same time. | Cap | Default | | - | - | | Per workflow | 10 concurrent executions | | Per workspace | 50 concurrent executions | Support can raise both caps. It is not self-serve, so [email support](mailto:support@nedzo.ai) with the number you need and why. ### What happens when you hit the cap Enroll a large batch and the runs past the cap sit in **Queued** instead of failing. They wait up to **3 hours** and start as soon as a slot frees up. Before this change they failed after about 14 seconds. An execution still queued after 3 hours fails. The error names the cap it waited on, so you know whether to split the batch or ask for a higher limit. ## What's tracked Each execution records: * **Trigger type and data** — What started the workflow and the data it carried * **Current position** — Which node is executing (or waiting) * **Step outputs** — The result of each completed step * **Error details** — What went wrong if a step failed * **Timestamps** — When it started, completed, or failed ## Step outputs Every action stores its output after running. These outputs are available to later steps as variables. For example, a voice call step stores: * Call ID * Status and outcome * Extracted data fields * Recording URL A webhook step stores: * HTTP status code * Response body ## Testing Individual steps can be tested from within their configuration panel. Step-level testing lets you verify each action works correctly before publishing. Test executions are flagged separately and don't count against limits. ## Troubleshooting ### Workflow didn't trigger * Check that the workflow is **published** and the **activate/deactivate toggle** is set to active * Verify the trigger conditions match the event * Check deduplication — the same trigger for the same contact within the dedup window (default 1 minute) is ignored ### Step failed * Check the error message in the execution details * Common causes: missing integration connection, invalid phone/email, expired OAuth token * Transient failures (network issues, rate limits) are retried automatically up to 3 times. Time spent waiting on the [concurrency cap](#concurrency-limits) is not a failure and does not spend a retry. ### Retry a failed run Open a failed run and click **Retry Execution**. The run picks up at the step that failed. Steps that finished before it do not run again, and their outputs are kept. The failed step runs again, so some runs can't be retried: * A step that may already have sent its message or call. That is a step that timed out, failed after it started sending, or placed a call that got no answer back. * A webhook that got a `5xx`, `408` or `429` answer, or no answer in time. A webhook your endpoint refused with another `4xx` can be retried, and it is sent again. You can also retry over the API. See [Retry a run](/api-reference/workflows/runs#retry-a-run). ### Follow-up call could not be scheduled When a [Voice call](/workflows/actions/voice-call) step is due a follow-up call and Nedzo can't schedule it, the run shows **Failed**. It does not show **Completed**. The call step reads *"Follow-up call N of M could not be scheduled"* and gives the reason. The run stops on that step, so no later step runs for a contact whose follow-ups were lost. It counts as a failed run, like any other step failure. ### Workflow stuck on "Waiting" The run is paused until a set time. Check the reason and time under **Waiting** in the Status column. The run picks up again at that time. If the time has long passed, the run shows **Stuck** instead. See [Stuck runs](#stuck-runs). # Workflows Source: https://docs.nedzo.ai/workflows/overview Automate multi-step sequences in Nedzo with triggers, actions, conditions, and delays. Build workflows that react to calls, contacts, and webhooks. Workflows let you automate what happens when something occurs in your workspace — a call ends, a contact is created, a tag is added, or a webhook fires. Chain triggers, actions, conditions, and delays together in a visual builder. ## How it works 1. **A trigger fires** — something happens (a call completes, a contact replies, etc.) 2. **Actions execute in order** — send an SMS, make a call, update a contact, post to Slack 3. **Conditions branch the flow** — take different paths based on data 4. **Delays pause when needed** — wait minutes, hours, or until a specific day ## Building workflows The workflow builder is a visual, node-based editor. Add a trigger, then drag actions, conditions, and delays into the flow. Each node has its own configuration panel. Workflows can be **published** and then **activated** or **deactivated** using the toggle in the editor toolbar (next to the Publish button). This lets you enable or disable a workflow without leaving the editor or deleting it. Deactivating a workflow stops it immediately: no new contacts are enrolled, and all in-progress executions are cancelled — including queued actions, delays, and scheduled follow-up calls. Cancelled executions appear in the execution history with a "Workflow deactivated" reason. A call that is already connected when you deactivate completes normally, but no new calls or messages are started. Reactivating the workflow does not resume cancelled executions — only new enrollments run. ## Learn more Events that start a workflow What your workflow can do Branching logic and timing controls Monitoring and troubleshooting runs # Contact created trigger Source: https://docs.nedzo.ai/workflows/triggers/contact-created Start a workflow when a new contact is added to your workspace, whether created manually, via the API, synced from GoHighLevel, or from an inbound call. Fires whenever a new contact is added to your workspace. This includes contacts created manually in the dashboard, imported via the API, synced from GoHighLevel, or created automatically by an incoming call or message. ## When it fires The trigger fires once per new contact, immediately after the contact record is created. It does not fire when an existing contact is updated. ## Configuration ### Filters Narrow down which new contacts should trigger the workflow. Click **Add Filter** to add conditions. | Filter field | Operators | Description | | - | - | - | | Email | equals, not equals, contains, not contains, is empty, is not empty | Match against the contact's email address | | Phone | equals, not equals, contains, not contains, is empty, is not empty | Match against the contact's phone number | | Has tag | equals, not equals | Check if the contact was created with a specific tag | | DND | equals, not equals | Check if the contact has Do Not Disturb (DND) on | All filters use AND logic — every filter must match for the workflow to run. **No filters:** If you don't add any filters, the workflow runs for every new contact. ### Examples **Only contacts with an email:** * Filter: Email → is not empty **Only contacts with a US phone number:** * Filter: Phone → starts with → `+1` **Exclude DND contacts:** * Filter: DND → not equals → `true` ## Data available When this trigger fires, the following data is available as variables in your workflow: | Variable | Description | Example | | - | - | - | | `{{contactId}}` | The contact's unique ID | `a1b2c3d4-e5f6-...` | | `{{firstName}}` | First name | `John` | | `{{lastName}}` | Last name | `Doe` | | `{{phone}}` | Phone number in E.164 format | `+14155551234` | | `{{email}}` | Email address | `john@example.com` | | `{{businessName}}` | Company name | `Acme Inc` | ## Deduplication Uses the contact ID as the dedup key. Default window: 1 minute. This means if the same contact is somehow created twice rapidly (e.g., race condition from two API calls), the workflow only runs once. ## Use case examples ### Welcome call to new leads A new contact is added → wait 5 minutes for them to settle in → call with your sales agent. 1. **Trigger:** Contact created 2. **Action:** [Wait](/workflows/actions/wait) — 5 minutes 3. **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent ### Sync new contacts to your CRM Every new contact is pushed to your external CRM via webhook. 1. **Trigger:** Contact created 2. **Action:** [Send webhook](/workflows/actions/send-webhook) — POST to your CRM API ```json theme={null} { "name": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "email": "{{email}}", "source": "nedzo" } ``` ### Welcome SMS Send a personalized text the moment a contact is created. 1. **Trigger:** Contact created 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Hi `{{firstName}}`, thanks for reaching out! We'll be in touch shortly."* # Contact replied trigger Source: https://docs.nedzo.ai/workflows/triggers/contact-replied Trigger a workflow when a contact sends an inbound message via SMS, WhatsApp, Email, Instagram, Messenger, or voice. Automate follow-up actions on replies. Fires when a contact sends a message through any connected messaging channel. Use this to react to inbound SMS, WhatsApp, Email, Instagram DMs, Messenger messages, or voice transcripts. ## When it fires The trigger fires immediately when a contact sends an inbound message through: * **SMS** — An incoming text message to one of your workspace phone numbers * **WhatsApp** — An incoming WhatsApp message to a connected number * **Voice** — A transcribed turn from a contact during a voice call * **Instagram** — A direct message on your connected Instagram account * **Messenger** — A message on your connected Facebook Page * **Email** — An incoming email to one of your agent's inbound email addresses The trigger does **not** fire when a chat conversation is finalized (use [Conversation Ended](/workflows/triggers/conversation-completed) for that) or for outbound messages sent by your team or AI. ## Configuration ### Filters | Filter field | Operators | Description | | - | - | - | | Reply channel | sms, whatsapp, voice, instagram, messenger, email | Only fire when the reply comes from a specific channel. Select the channel directly — no value field needed. | | Message | equals, not equals, contains, not contains, is empty, is not empty | Match against the message content. Comparisons are case-insensitive. | All filters use AND logic. ### Examples **Only SMS replies:** * Filter: Reply channel → sms **Instagram messages containing "pricing":** * Filter: Reply channel → instagram * Filter: Message → contains → `pricing` **Any reply with content (not empty):** * Filter: Message → is not empty **Any reply from any channel:** * No filters. Leave it empty. ## Data available | Variable | Description | Example | | - | - | - | | `{{contactId}}` | The contact's ID | `a1b2c3d4-...` | | `{{firstName}}` | Contact's first name | `John` | | `{{lastName}}` | Contact's last name | `Doe` | | `{{phone}}` | Contact's phone number | `+14155551234` | | `{{email}}` | Contact's email | `john@example.com` | | `{{trigger.reply.message}}` | The message text the contact sent | `Yes, I'm interested` | | `{{trigger.reply.channel}}` | Which channel the reply came from | `sms` | ## Deduplication Uses the contact ID + conversation ID as the dedup key. Default window: 1 minute. If a contact sends multiple messages quickly in the same conversation, only the first one triggers the workflow. ## Use case examples ### Notify sales on any SMS reply Every inbound SMS gets posted to Slack so the team can follow up. 1. **Trigger:** Contact replied → Reply channel: sms 2. **Action:** [Slack message](/workflows/actions/slack-message) — #sales channel * Message: *"SMS from `{{firstName}}` `{{lastName}}` (`{{phone}}`): `{{trigger.reply.message}}`"* ### Auto-tag engaged contacts When a contact replies on any channel, mark them as engaged. 1. **Trigger:** Contact replied (no filters) 2. **Action:** [Update contact](/workflows/actions/update-contact) — Add "engaged" tag ### Route by channel Different channels get different handling. 1. **Trigger:** Contact replied (no filters) 2. **Action:** [Condition](/workflows/actions/condition) * **Path: SMS** — `{{trigger.reply.channel}}` equals `sms` → [Slack message](/workflows/actions/slack-message) to #sms-replies * **Path: Instagram** — `{{trigger.reply.channel}}` equals `instagram` → [Slack message](/workflows/actions/slack-message) to #social * **Default** → [Slack message](/workflows/actions/slack-message) to #general ### Re-engage after reply When a contact replies, schedule a follow-up call. 1. **Trigger:** Contact replied → any channel 2. **Action:** [Update contact](/workflows/actions/update-contact) — Add "engaged" tag 3. **Action:** [Wait](/workflows/actions/wait) — 10 minutes 4. **Action:** [Voice call](/workflows/actions/voice-call) — Follow-up agent # Contact tag trigger Source: https://docs.nedzo.ai/workflows/triggers/contact-tagged Start a workflow when a tag is added to or removed from a contact. Launch campaigns, onboarding sequences, or status-change automations. Fires when a tag is added to or removed from a contact. This is one of the most flexible triggers — use it to start campaigns, run onboarding sequences, or react to status changes. ## When it fires The trigger fires immediately when: * A tag is **added** to a contact (manually, via API, or from another workflow) * A tag is **removed** from a contact You configure whether the workflow responds to additions, removals, or both. ### Adding tags through the API Tags added through the REST API fire this trigger the same way a dashboard tag does: | Endpoint | What fires | | - | - | | [`POST /contacts`](/api-reference/contacts/create) | [Contact created](/workflows/triggers/contact-created), with the tags already attached | | [`PATCH /contacts/{id}`](/api-reference/contacts/update) | **Contact tagged**, once per newly added tag | | [`POST /contacts/upsert`](/api-reference/contacts/upsert) | **Contact tagged** on a matched contact; Contact created on a new one | Only tags the contact doesn't already have fire the trigger. If your CRM re-pushes the same contact and tags on every sync, the repeat tags fire nothing — so a follow-up workflow won't run twice on the same contact. Bulk contact imports deliberately don't fire this trigger. Importing a tagged list won't start a workflow for every row. ## Configuration ### Tag event Choose the event type: | Event | Description | | - | - | | Tag Added | Fires when a tag is added to a contact | | Tag Removed | Fires when a tag is removed from a contact | You can create separate workflows for additions and removals, or use a single workflow with a [Condition](/workflows/actions/condition) to branch. ### Tag filter Select which tags should trigger the workflow: | Setting | Description | | - | - | | Specific tags | Select one or more tags from the dropdown. The workflow only fires when one of these tags is added/removed. | | Any tag | Leave the tag filter empty. The workflow fires on any tag change. | ### Examples **Only fire on "hot-lead" tag:** * Event: Tag Added * Tag: Select "hot-lead" from dropdown **Fire when any tag is removed:** * Event: Tag Removed * Tag: Leave empty (matches all) **Fire on multiple tags:** * Event: Tag Added * Tags: Select "interested", "qualified", "ready-to-buy" ## Data available | Variable | Description | Example | | - | - | - | | `{{contactId}}` | The contact's ID | `a1b2c3d4-...` | | `{{firstName}}` | Contact's first name | `John` | | `{{lastName}}` | Contact's last name | `Doe` | | `{{phone}}` | Contact's phone number | `+14155551234` | | `{{email}}` | Contact's email | `john@example.com` | | `{{trigger.tag.id}}` | The tag's ID | `t1a2b3c4-...` | | `{{trigger.tag.name}}` | The tag's name | `hot-lead` | | `{{trigger.tag.mode}}` | Whether it was added or removed | `added` | ## Deduplication Uses the contact ID + tag ID as the dedup key. Default window: 1 minute. If the same tag is toggled on and off quickly, only the first event triggers the workflow. ## Use case examples ### Start outreach when tagged "hot lead" A sales rep tags a contact → the agent calls them automatically. 1. **Trigger:** Contact tagged → Tag Added → "hot-lead" 2. **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent 3. **Action:** [Condition](/workflows/actions/condition) — Check call outcome * **Outcome = no\_answer:** [Send SMS](/workflows/actions/send-sms) — *"Hi `{{firstName}}`, I just tried to reach you. When's a good time to chat?"* * **Outcome = voicemail:** [Wait](/workflows/actions/wait) 4 hours → [Voice call](/workflows/actions/voice-call) retry * **Default:** [Update contact](/workflows/actions/update-contact) — Add "contacted" tag ### Onboarding drip sequence New customer tagged → send a series of emails over several days. 1. **Trigger:** Contact tagged → Tag Added → "new-customer" 2. **Action:** [Send email](/workflows/actions/send-email) — Welcome email 3. **Action:** [Wait](/workflows/actions/wait) — 1 day 4. **Action:** [Send email](/workflows/actions/send-email) — Getting started guide 5. **Action:** [Wait](/workflows/actions/wait) — 3 days 6. **Action:** [Voice call](/workflows/actions/voice-call) — Onboarding check-in agent ### Notify team when tag removed When a VIP tag is removed, alert the team. 1. **Trigger:** Contact tagged → Tag Removed → "vip" 2. **Action:** [Slack message](/workflows/actions/slack-message) — *"`{{firstName}}` `{{lastName}}` is no longer a VIP. Tag was removed."* 3. **Action:** [Send webhook](/workflows/actions/send-webhook) — Update CRM status # Conversation ended trigger Source: https://docs.nedzo.ai/workflows/triggers/conversation-completed Trigger a workflow when an AI conversation ends — voice calls, chat on any messaging channel, or web agent sessions. Access transcript and summary. Fires when any AI conversation ends — voice calls, chat conversations on any connected messaging channel, or web agent sessions. This is one of the most powerful triggers — it gives you access to the full conversation data including transcript, summary, outcome, and any extracted fields. This trigger was previously called **Call Completed** (voice-only) and **Conversation Completed**. Existing workflows continue to work unchanged. The internal type identifier is still `conversationCompleted`. ## When it fires The trigger fires immediately after a conversation ends: | Agent type | Fires after | | - | - | | Voice | A phone call ends, regardless of outcome (answered, voicemail, no answer, or failed). Both inbound and outbound. | | Chat | An SMS, WhatsApp, Instagram DM, Messenger, email, or web chat conversation is finalized. | | Web | A web agent session (voice or chat widget embedded on your site) ends. | ## Configuration ### Filters Use filters to only trigger the workflow for specific conversations. All filters use AND logic. | Filter field | Options | Description | | - | - | - | | Agent Type | voice, chat, web (multi-select) | Only fire for the selected agent types. Leave empty to fire for all types. | | AI Agent | Select agents | Only fire for conversations handled by specific agents | | Call direction | Inbound, Outbound | **Voice only.** Only fire for calls in a specific direction | | Outcome | One of the fixed [Outcomes](/agents/call-analysis#outcome), with `is` / `is not` | Match against the conversation's Outcome. Case-insensitive. | | Min duration | Number (seconds) | **Voice only.** Only fire if the call lasted at least this long | | Max duration | Number (seconds) | **Voice only.** Only fire if the call was shorter than this | | Extracted data | Free-text per extraction field with `equals` / `not equals` / `contains` / `not contains` / `is empty` / `is not empty` / `greater than` / `less than` operators | Match against any extraction field configured on the agent (budget, lead status, appointment date, etc.). Available across voice, chat, and web. See [Extracted data filters](#extracted-data-filters) below. | Voice-only filters (direction, duration) only apply when Agent Type includes voice. If you select only `chat` or `web`, those filters are ignored. The Outcome and Extracted data filters work on every channel. ### Outcome Every conversation is classified into one [Outcome](/agents/call-analysis#outcome) from a fixed list (`Appointment booked`, `Booking failed`, `Transferred`, `No answer`, `Voicemail`, `Follow up`, `Callback requested`, `Not interested`, `Disqualified`, `Do not disturb`, `Wrong number`, `Other`). The filter is a dropdown of those values. | Operator | Behavior | | - | - | | `is` | Exact match, case-insensitive | | `is not` | Negated exact match, case-insensitive | This filter was previously called **Call disposition** and took free text. Filters saved before the rename are still evaluated as they were saved, but they now match against the fixed Outcome list: * A filter with no operator and a legacy value such as `completed` or `appointment_booked` still matches the conversation's stored call result. * Every other saved filter, including `contains` and custom labels such as `Interested` or `Hot Lead`, is compared with the conversation's Outcome. It only matches when its value fits one of the fixed Outcomes (case-insensitive; `contains` matches part of the Outcome). * A filter saved with `Do not call` matches `Do not disturb`. * `Positive` is no longer an outcome, so a filter on `Positive` stops matching new conversations. Open older workflows that filter on Outcome and pick a value from the list, so they keep firing. For voice-specific routing on raw call ending reasons (voicemail, no answer, transferred, etc.), use a [Condition](/workflows/actions/condition) step on `{{trigger.conversation.endedReason}}` — see the voice-only fields table further down. ### Extracted data filters Filter on any extraction field configured on the agent — budget, lead status, appointment date, sentiment, anything you've set up. The filter picker shows every extraction field defined on the agents your workflow listens to, and works across voice, chat, and web conversations. | Operator | Behavior | | - | - | | `equals` / `not equals` | Exact match, case-insensitive. Booleans and numbers are compared as strings (`true`, `42`). | | `contains` / `not contains` | Substring match, case-insensitive. | | `is empty` / `is not empty` | Matches when the AI didn't extract a value (missing key, null, empty string, empty array, or empty object). | | `greater than` / `less than` | Numeric comparison. Both sides must be numeric — non-numeric values never match. | Behavior notes: * Filtering only fires the workflow when the field matches. Filters are AND-combined — every filter must match. * `not equals` and `not contains` against a missing value match (vacuous truth) — useful for "fire unless the AI said X". * Extracted values are also available as variables in your actions — see [`{{trigger.conversation.metadata.fieldName}}`](#conversation-variables) further down. ### Examples **Only outbound voice calls that booked an appointment:** * Filter: Agent Type → voice * Filter: Call direction → Outbound * Filter: Outcome → `is` `Appointment booked` **All chat conversations from a specific agent:** * Filter: Agent Type → chat * Filter: AI Agent → Select your support agent **Voice or web sessions longer than 30 seconds:** * Filter: Agent Type → voice, web * Filter: Min duration → 30 **Every conversation on every channel:** * No filters. Leave it empty. **Hot leads only (any channel):** * Filter: Extracted data → `lead_status` `contains` `hot` **Conversations where appointment date was captured:** * Filter: Extracted data → `appointment_date` `is not empty` **High-budget leads:** * Filter: Extracted data → `budget` `greater than` `5000` ## Data available This trigger provides the richest data of any trigger type. Some fields are only present for certain agent types — use the Agent Type filter or a Condition step to branch safely. ### Contact variables Available for every conversation regardless of channel. | Variable | Legacy alias | Description | Example | | - | - | - | - | | `{{contact.contactId}}` | `{{contactId}}` | The contact's ID | `a1b2c3d4-...` | | `{{contact.firstName}}` | `{{firstName}}` | Contact's first name | `John` | | `{{contact.lastName}}` | `{{lastName}}` | Contact's last name | `Doe` | | `{{contact.phone}}` | `{{phone}}` | Contact's phone number | `+14155551234` | | `{{contact.email}}` | `{{email}}` | Contact's email | `john@example.com` | Dot notation (`{{contact.firstName}}`) is now the standard. The flat aliases on the right still work for backwards compatibility — existing workflows continue to render unchanged. ### Conversation variables The canonical namespace for all channels. Use these in new workflows. | Variable | Description | Example | | - | - | - | | `{{trigger.conversation.conversationId}}` | Unique conversation ID | `0a9b8c7d-...` | | `{{trigger.conversation.channel}}` | Channel the conversation happened on | `voice`, `sms`, `whatsapp`, `instagram`, `messenger`, `email`, `web_chat`, `web` | | `{{trigger.conversation.agentType}}` | Deprecated. Agents no longer carry a type, so this renders empty on voice conversations. Use `{{trigger.conversation.channel}}` instead. | *(empty)* | | `{{trigger.conversation.agentId}}` | Agent that handled the conversation | `x1y2z3-...` | | `{{trigger.conversation.agentName}}` | Agent's name | `Sales Agent` | | `{{trigger.conversation.startedAt}}` | When it started (ISO 8601) | `2026-04-23T14:30:00Z` | | `{{trigger.conversation.endedAt}}` | When it ended (ISO 8601) | `2026-04-23T14:34:05Z` | | `{{trigger.conversation.summary}}` | AI-generated summary | `Discussed pricing...` | | `{{trigger.conversation.transcript}}` | Full transcript (text-channel lines are timestamp-prefixed in the workspace timezone, e.g. `[2026-06-15 14:23:07] User: Hi...`; voice/web lines are not) | `[2026-06-15 14:23:07] User: Hi...` | | `{{trigger.call.analysis.outcome}}` or `{{trigger.call.analysis.disposition}}` | The conversation's [Outcome](/agents/call-analysis#outcome). Picking **Outcome** in the variable picker inserts `{{trigger.call.analysis.outcome}}`. `{{trigger.call.analysis.disposition}}` is an old name for the same value. It still works for now, but use `outcome` in new workflows. | `Appointment booked` | | `{{trigger.conversation.metadata}}` | Custom extraction fields | `{"budget": "5000"}` | Extraction fields configured on the agent (budget, timeline, etc.) are available under `{{trigger.conversation.metadata.fieldName}}`. ### Voice-only fields Present when `agentType` is `voice`. | Variable | Description | Example | | - | - | - | | `{{trigger.conversation.callId}}` | Voice Engine call ID | `c1d2e3f4-...` | | `{{trigger.conversation.duration}}` | Call length in seconds | `245` | | `{{trigger.conversation.direction}}` | Inbound or outbound | `outbound` | | `{{trigger.conversation.endedReason}}` | Raw reason the call ended | `customer-ended-call` | Use `{{trigger.call.analysis.outcome}}` for the conversation's Outcome, or `{{trigger.conversation.endedReason}}` for the raw call ending reason. ### Appointment variables Present when the agent's booking tool confirmed an appointment during the conversation, and empty otherwise. These live under `{{trigger.call.*}}`. | Variable | Description | Example | | - | - | - | | `{{trigger.call.appointmentDateFormatted}}` | Appointment date, in your workspace timezone | `Sep 10, 2026` | | `{{trigger.call.appointmentTime}}` | Appointment time, in your workspace timezone | `6:00 PM` | | `{{trigger.call.appointmentDate}}` | Appointment date and time as an ISO 8601 UTC timestamp | `2026-09-10T22:00:00+00:00` | Use the first two in emails, SMS and Slack messages, e.g. `Appointment: {{trigger.call.appointmentDateFormatted}} at {{trigger.call.appointmentTime}}`. Use the ISO value where another system or field needs a machine-readable date: webhook bodies, date-type contact fields, and `greater than` / `less than` conditions. If a workspace hasn't set a timezone, `appointmentDateFormatted` and `appointmentTime` render empty — there's no silent UTC fallback. ### Chat-only fields Present when `agentType` is `chat`. | Variable | Description | Example | | - | - | - | | `{{trigger.conversation.messageCount}}` | Number of messages exchanged | `8` | | `{{trigger.conversation.lastMessageAt}}` | Timestamp of the final message (ISO 8601) | `2026-04-23T14:45:00Z` | Chat conversations do not include `direction` or `duration` — use the Outcome and timestamps instead. ### Web-only fields Present when `agentType` is `web`. Web sessions use the voice-style fields (`callId`, `duration`, `endedReason`) plus `channel: "web"`. ### Legacy `trigger.call.*` namespace For backwards compatibility, existing voice workflows can still use the `{{trigger.call.*}}` namespace. It maps 1:1 to `{{trigger.conversation.*}}` for voice-only fields (`callId`, `duration`, `direction`, `summary`, `transcript`, `agentId`, `agentName`, `agentType`, `startedAt`, `endedAt`, `endedReason`, `metadata`). New workflows should use `{{trigger.conversation.*}}` since the legacy namespace only resolves for voice conversations. `{{trigger.call.agentType}}` renders empty, the same as its `{{trigger.conversation.*}}` counterpart. Agents no longer carry a type. ## Deduplication Uses the conversation ID as the dedup key. Default window: 1 minute. Each conversation can only trigger the workflow once within the window. ## Use case examples ### Follow-up based on voice outcome Different outcomes get different follow-up strategies. 1. **Trigger:** Conversation ended → Agent Type: voice 2. **Action:** [Condition](/workflows/actions/condition) — Check **Outcome** (Trigger) * **Path: is `Follow up`** * [Send email](/workflows/actions/send-email) — *"Hi `{{firstName}}`, great chatting! Here are the details we discussed..."* * [Update contact](/workflows/actions/update-contact) — Add "interested" tag * **Path: is `Voicemail`** * [Wait](/workflows/actions/wait) — 4 hours * [Voice call](/workflows/actions/voice-call) — Retry * **Path: is `No answer`** * [Send SMS](/workflows/actions/send-sms) — *"Hi `{{firstName}}`, I just tried to reach you. Let me know a good time."* * **Default** * [Update contact](/workflows/actions/update-contact) — Add "needs-review" tag ### Post SMS summary to Slack Every finalized SMS conversation gets a summary posted to your team channel. 1. **Trigger:** Conversation ended → Agent Type: chat 2. **Action:** [Condition](/workflows/actions/condition) — `{{trigger.conversation.channel}}` equals `sms` 3. **Action:** [Slack message](/workflows/actions/slack-message) — #sms-conversations * Message: *"SMS with `{{firstName}}` `{{lastName}}` (`{{trigger.conversation.messageCount}}` messages, `{{trigger.call.analysis.outcome}}`)\n\n`{{trigger.conversation.summary}}`"* ### Post-call summary to Slack Every completed voice call gets a summary posted to your team channel. 1. **Trigger:** Conversation ended → Agent Type: voice 2. **Action:** [Slack message](/workflows/actions/slack-message) — #call-updates * Message: *"Call with `{{firstName}}` `{{lastName}}` (`{{trigger.conversation.duration}}`s, `{{trigger.call.analysis.outcome}}`)\n\n`{{trigger.conversation.summary}}`"* ### Sync conversation data to CRM Push conversation details to your CRM after every conversation, regardless of channel. 1. **Trigger:** Conversation ended (no filters) 2. **Action:** [Send webhook](/workflows/actions/send-webhook) — POST to CRM ```json theme={null} { "contact": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "channel": "{{trigger.conversation.channel}}", "agentType": "{{trigger.conversation.agentType}}", "outcome": "{{trigger.call.analysis.outcome}}", "summary": "{{trigger.conversation.summary}}", "agent": "{{trigger.conversation.agentName}}" } ``` ### Do Not Disturb compliance A conversation whose outcome is `Do not disturb` (the contact asked not to be contacted) turns on Do Not Disturb (DND) for that contact automatically, on every channel: calls, chat, SMS, email and the rest. Nothing more is sent to them. A `Not interested` outcome does not turn on DND. Use a workflow to tell your team. 1. **Trigger:** Conversation ended → Outcome: `is` `Do not disturb` 2. **Action:** [Slack message](/workflows/actions/slack-message) to #compliance * Message: *"`{{firstName}}` `{{lastName}}` (`{{phone}}`) asked not to be contacted. Do Not Disturb is now on."* ### Tag engaged web visitors When a web agent session finishes with a Follow up outcome, mark the contact as engaged. 1. **Trigger:** Conversation ended → Agent Type: web 2. **Action:** [Condition](/workflows/actions/condition) — **Outcome** (Trigger) is `Follow up` 3. **Action:** [Update contact](/workflows/actions/update-contact) — Add "engaged-web" tag # Workflow triggers Source: https://docs.nedzo.ai/workflows/triggers/overview Explore the events that start a Nedzo workflow, including webhooks, schedules, contact creation, tag changes, inbound messages, and completed calls. A trigger is the event that kicks off your workflow. Every workflow starts with exactly one trigger node. When the event occurs, Nedzo checks if any active, published workflows match, and runs them. ## Available triggers | Trigger | Fires when... | | - | - | | [Contact Created](/workflows/triggers/contact-created) | A new contact is added to your workspace | | [Contact Tagged](/workflows/triggers/contact-tagged) | A tag is added to or removed from a contact | | [Contact Replied](/workflows/triggers/contact-replied) | A contact sends an inbound message | | [Conversation Ended](/workflows/triggers/conversation-completed) | A voice call, chat conversation, or web agent session ends | | [Webhook](/workflows/triggers/webhook) | An external system sends a POST request | | [Schedule](/workflows/triggers/schedule) | A set time arrives (one-time or recurring) | | Manual | You add contacts from the Contacts page with **Add to workflow** | ## Manual trigger A workflow with the **Manual** trigger runs only when you start it. On the **Contacts** page, select contacts, then click **Add to workflow** in the bulk bar and pick the workflow. The workflow must be published and turned on. Nedzo tells you how many contacts it started for, how many were already in it, and how many could not be added. ## Filters Most triggers support filters that narrow down when the workflow should run. Filters use AND logic — all filters must match for the workflow to fire. If you don't add any filters, the trigger fires on every matching event. ## Deduplication By default, triggers include a **1-minute deduplication window**. If the same event fires multiple times for the same contact within that window, only the first one runs the workflow. | Setting | Default | Range | | - | - | - | | Enabled | Yes | On/Off | | Window | 1 minute | 1 minute – 24 hours | You can adjust the window or disable deduplication entirely in the trigger settings. # Schedule trigger Source: https://docs.nedzo.ai/workflows/triggers/schedule Run Nedzo workflows on a time-based schedule, either one-time or recurring. Target contact segments with hourly, daily, or weekly cadences. Fires on a time-based schedule. Use it to run workflows at a specific date and time, or on a recurring cadence like every hour, daily, or weekly. Schedule triggers run against your contacts, so you can target specific segments with filters. ## When it fires The trigger fires at the scheduled time. For one-time schedules, it fires once and stops. For recurring schedules, it fires on every interval until you deactivate the workflow or the end date is reached. When the trigger fires, it creates one workflow execution per contact that matches your contact filters. If no filters are set, it runs for all contacts in the workspace. ## Configuration ### Schedule mode Choose between two modes: | Mode | Description | | - | - | | **One-time** | Fires once at a specific date and time | | **Recurring** | Fires repeatedly on a set interval | ### One-time schedule | Field | Required | Description | | - | - | - | | Date & time | Yes | When the workflow should run. Must be at least 1 minute in the future. | | Timezone | Yes | IANA timezone for the scheduled time (e.g., `America/New_York`, `Europe/Amsterdam`) | ### Recurring schedule | Field | Required | Description | | - | - | - | | Frequency | Yes | How often to run: `minutes`, `hours`, `days`, `weekly`, or `monthly` | | Interval | Depends | Number of units between runs. For `minutes`, minimum interval is 5. | | Cron pattern | Alternative | Advanced: set a custom cron expression instead of using frequency/interval | | Timezone | Yes | IANA timezone for the schedule | | End date | No | Optional date when the recurring schedule stops running | **Frequency examples:** | Frequency | Interval | Result | | - | - | - | | minutes | 30 | Every 30 minutes | | hours | 2 | Every 2 hours | | days | 1 | Once a day | | weekly | 1 | Once a week | | monthly | 1 | Once a month | For `minutes` frequency, the minimum interval is 5 minutes to prevent excessive workflow executions. ### Contact filters Narrow down which contacts the workflow runs for. Add filters to target specific segments instead of running for every contact in your workspace. Each filter has three parts: | Part | Description | | - | - | | **Field** | The contact field to check (e.g., email, phone, tag) | | **Operator** | How to compare (equals, not equals, contains, is empty, etc.) | | **Value** | The value to compare against | All filters use AND logic — every filter must match for the contact to be included. **No filters:** If you don't add any filters, the workflow runs for every contact in the workspace. ## Data available When this trigger fires, the following data is available as variables in your workflow: | Variable | Description | Example | | - | - | - | | `{{contactId}}` | The contact's unique ID | `a1b2c3d4-e5f6-...` | | `{{firstName}}` | First name | `John` | | `{{lastName}}` | Last name | `Doe` | | `{{phone}}` | Phone number in E.164 format | `+14155551234` | | `{{email}}` | Email address | `john@example.com` | | `{{businessName}}` | Company name | `Acme Inc` | ## Schedule status After publishing a workflow with a schedule trigger, you can see the schedule status on the workflow: | Field | Description | | - | - | | **Next run** | When the workflow will fire next | | **Last run** | When the workflow last fired | | **Active** | Whether the schedule is currently running | Deactivating the workflow pauses the schedule. Reactivating it resumes from the next scheduled time. ## Use case examples ### One-time campaign blast Send an SMS to all contacts tagged "promo-list" at a specific date and time. 1. **Trigger:** Schedule (One-time) — January 15, 2026 at 10:00 AM EST * Filter: Has tag → equals → "promo-list" 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Hi `{{firstName}}`, we have a special offer for you! Reply YES to learn more."* ### Daily follow-up calls Call contacts who haven't been reached yet, every day at 9 AM. 1. **Trigger:** Schedule (Recurring) — Daily at 9:00 AM * Filter: Has tag → equals → "needs-call" * Filter: Has tag → not equals → "called" 2. **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent 3. **Action:** [Update contact](/workflows/actions/update-contact) — Add "called" tag ### Weekly check-in SMS Send a weekly check-in message to active customers. 1. **Trigger:** Schedule (Recurring) — Weekly, Monday at 10:00 AM * Filter: Has tag → equals → "active-customer" 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Hi `{{firstName}}`, hope your week is off to a great start! Let us know if you need anything."* ### Monthly renewal reminder Email customers 30 days before their subscription renews. 1. **Trigger:** Schedule (Recurring) — Monthly * Filter: Has tag → equals → "renewal-due" 2. **Action:** [Send email](/workflows/actions/send-email) * Subject: *"Your subscription renewal is coming up"* * Body: *"Hi `{{firstName}}`, your subscription renews next month. Let us know if you have any questions."* 3. **Action:** [Update contact](/workflows/actions/update-contact) — Remove "renewal-due" tag # Webhook trigger Source: https://docs.nedzo.ai/workflows/triggers/webhook Start a Nedzo workflow from any external system using an HTTP POST request. Connect tools like Zapier, Make, n8n, or your own backend to trigger automations. Fires when an external system sends an HTTP POST request to the workflow's unique webhook URL. Use this to connect Nedzo to any external tool — Zapier, Make, n8n, your own backend, or anything that can send HTTP requests. ## When it fires The trigger fires immediately when a valid POST request hits the webhook URL. Each request creates one workflow execution. Unlike other triggers, webhook triggers are not deduplicated — every request runs the workflow. ## Configuration When you add a webhook trigger, Nedzo generates two things: | Field | Description | | - | - | | **Webhook URL** | The unique URL to send requests to. This stays the same even if you regenerate the token or change auth. | | **Webhook token** | A 64-character secret embedded in the URL path. Cryptographically identifies the caller. | ### Regenerating the token Click **Regenerate** in the trigger settings to create a new token. The old token stops working immediately. The URL stays the same. ### Authorization The 64-character token in the URL authenticates the caller by itself. For extra protection, configure an additional authorization scheme from the trigger settings. | Scheme | How it works | | - | - | | **None** (default) | Token in the URL is the only credential. Fine for most integrations — the token is 256 bits of entropy. | | **HMAC** | Caller signs each request with a shared secret using HMAC-SHA256. Timestamp-bound so replayed requests are rejected. Recommended if you need tamper-proof payloads. | | **Bearer** | Caller sends a static token in the `Authorization: Bearer ` header. | | **Basic** | Caller sends `Authorization: Basic ` using HTTP Basic auth. | Failed auth attempts are rate-limited per trigger. After too many bad attempts in a short window the trigger returns `401` until the cooldown ends. #### HMAC details When HMAC is selected, Nedzo gives you a shared secret and two header names (defaults: `X-Nedzo-Signature`, `X-Nedzo-Timestamp`). To sign a request: 1. Take the current Unix timestamp in seconds (e.g. `1730000000`). 2. Build the signed string: `{timestamp}.{rawRequestBody}`. 3. Compute `HMAC-SHA256(secret, signedString)` and hex-encode the result (64 characters). 4. Send the hex digest in the signature header and the timestamp in the timestamp header. Example (Node.js): ```javascript theme={null} import crypto from 'crypto'; const timestamp = Math.floor(Date.now() / 1000); const body = JSON.stringify({ firstName: 'John', phone: '+14155551234' }); const signature = crypto .createHmac('sha256', SHARED_SECRET) .update(`${timestamp}.${body}`) .digest('hex'); await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Nedzo-Timestamp': String(timestamp), 'X-Nedzo-Signature': signature, }, body, }); ``` Requests with a timestamp more than 5 minutes off from the server clock are rejected. Each signature can only be used once inside the tolerance window (replay protection). ### Sample payload The trigger UI lets you capture a **sample payload**. Send a test request to the webhook URL, and Nedzo captures the payload structure. This is used to: * Show available fields in the condition builder * Provide autocomplete for variable references in actions * Auto-detect contact fields (email, phone) for downstream contact-scoped actions ### Contact field mapping If your workflow has a [Make phone call](/workflows/actions/voice-call), [Send SMS](/workflows/actions/send-sms), [Send email](/workflows/actions/send-email), or [Update contact](/workflows/actions/update-contact) action set to "Contact", the webhook payload itself supplies the contact data — there's no contact in scope at trigger time. The trigger config shows a **Contact fields** section with one row per required field, based on which actions you've added downstream. The required fields depend on the action: * **Send SMS / Make phone call** require **Phone**. * **Send email** requires **Email**. * **Update contact** (create or update) requires an **identifier** — either **Phone** or **Email**. Update contact resolves the contact by phone first, then email, so mapping either one is enough. The workflow no longer silently creates a blank contact when neither is present. For each field, Nedzo: 1. **Auto-detects** common field names from the captured sample payload — `email` / `emailAddress` / `email_address` for Email, and `phone` / `phoneNumber` / `phone_number` for Phone. Auto-detection is case-insensitive and works on top-level fields. 2. **Falls back to a dropdown** of every string-valued path in the sample payload, so you can map a non-standard field. Useful for nested values like `data.lead.cell` or aliases like `mobile`. If a required field can't be resolved (no auto-detect match, no explicit mapping, no `contactId` in the payload), the **Publish** button is disabled with a tooltip explaining what's missing, and the webhook trigger node shows an attention indicator on the canvas. Once every required field is resolvable, Publish becomes available. #### Bypass mapping with `contactId` If your payload includes a `contactId` that matches an existing contact, that takes precedence — the Email and Phone mapping is bypassed entirely and the contact is loaded from the database. Useful when the upstream system already knows the Nedzo contact ID. ```json theme={null} { "contactId": "a1b2c3d4-..." } ``` ## Sending a request Send an HTTP POST with a JSON body. The 64-character token is part of the URL path: ```bash theme={null} curl -X POST "https://api.nedzo.ai/webhooks/trigger/{webhook-token}" \ -H "Content-Type: application/json" \ -d '{ "firstName": "John", "lastName": "Doe", "phone": "+14155551234", "email": "john@example.com", "dealValue": 5000, "source": "website-form" }' ``` If the trigger is configured with HMAC, Bearer, or Basic auth, add the required headers as well — see the [Authorization](#authorization) section above. ### Request requirements | Requirement | Details | | - | - | | Method | POST only | | Content-Type | `application/json` | | Body | Valid JSON, max 1 MB | | Extra auth headers | Only required if the trigger has HMAC/Bearer/Basic enabled | ## Data available Every field you send in the request body is available as a variable using the `{{trigger.webhook.body.*}}` pattern. For the example payload above: | Variable | Value | | - | - | | `{{trigger.webhook.body.firstName}}` | `John` | | `{{trigger.webhook.body.lastName}}` | `Doe` | | `{{trigger.webhook.body.phone}}` | `+14155551234` | | `{{trigger.webhook.body.email}}` | `john@example.com` | | `{{trigger.webhook.body.dealValue}}` | `5000` | | `{{trigger.webhook.body.source}}` | `website-form` | If you include a `contactId` in the payload and it matches an existing contact, the contact's standard fields (`{{firstName}}`, `{{phone}}`, etc.) are also loaded. Otherwise, contact-scoped actions resolve the contact from the email/phone fields you mapped in the trigger config — see [Contact field mapping](#contact-field-mapping) above. ## Use case examples ### Trigger from Zapier on form submission Connect a Zapier Zap to send form data to Nedzo and call the lead. 1. **In Zapier:** Create a Zap with your form tool as trigger, and a Webhook action pointing to your Nedzo webhook URL 2. **Map fields:** firstName, lastName, phone, email 3. **In Nedzo:** * **Trigger:** Webhook * **Action:** [Update contact](/workflows/actions/update-contact) — Create contact from webhook data * First name: `{{trigger.webhook.body.firstName}}` * Phone: `{{trigger.webhook.body.phone}}` * Tags: "website-lead" * **Action:** [Wait](/workflows/actions/wait) — 2 minutes * **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent ### Trigger from your backend on deal close Your app sends a POST when a deal closes. 1. **Trigger:** Webhook 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Congrats `{{trigger.webhook.body.firstName}}`, your deal is confirmed! We'll be in touch with next steps."* 3. **Action:** [Slack message](/workflows/actions/slack-message) — #wins * Message: *"Deal closed: `{{trigger.webhook.body.firstName}}` `{{trigger.webhook.body.lastName}}` — \$`{{trigger.webhook.body.dealValue}}`"* ### Trigger from Make (Integromat) Use a Make HTTP module to call the webhook URL after any automation step. 1. **In Make:** Add an HTTP "Make a request" module 2. **URL:** Your Nedzo webhook URL 3. **Method:** POST 4. **Body:** JSON with your data 5. **In Nedzo:** Build any workflow using the incoming data ### Manual trigger via API Use the webhook URL from your own code or Postman for testing or custom integrations. ```javascript theme={null} const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contactId: 'existing-contact-id', customData: 'anything you need' }) }); ```