type UUID = string; type ISODateString = string; interface ClientOptions { /** Your Nexor API key. Get one in the dashboard at https://app.getnexor.ai */ apiKey: string; /** Override the API base URL. Defaults to https://api.getnexor.ai */ baseUrl?: string; /** Per-request timeout in ms. Defaults to 30000. */ timeoutMs?: number; /** Number of automatic retries on 5xx / network errors. Defaults to 2. */ maxRetries?: number; /** Custom fetch implementation (e.g. for testing). Defaults to globalThis.fetch. */ fetch?: typeof fetch; /** Optional user-agent suffix appended to the default. */ userAgentSuffix?: string; } interface RequestOptions { /** AbortSignal for cancellation. */ signal?: AbortSignal; /** Override per-request timeout. */ timeoutMs?: number; /** Override the client's retry count for this request. Set 0 to disable * retries on non-idempotent calls (e.g. a chat turn, which re-runs the LLM * and re-inserts the message if retried). */ maxRetries?: number; /** Optional idempotency key (echoed back to server logs). */ idempotencyKey?: string; } interface ForceFirstMessage { channel: "whatsapp" | "email"; content: string; /** Required when channel === "email". */ subject?: string; /** Override the default email sender. */ sender_id?: UUID; } interface CreateLeadInput { first_name: string; last_name?: string; email?: string; phone?: string; source?: string; company?: string; title?: string; city?: string; date_of_birth?: ISODateString; external_id?: string; metadata?: Record; tags?: string[] | string; workflow_id?: UUID; campaign_id?: UUID; force_first_message?: ForceFirstMessage; /** Any extra fields are merged into metadata server-side. */ [extra: string]: unknown; } interface Lead { id: UUID; first_name: string; last_name: string | null; email: string | null; phone: string | null; source: string; company: string | null; title: string | null; metadata: Record; created_at: ISODateString; updated_at?: ISODateString; campaign_id?: UUID | null; tags?: unknown; } interface WorkflowRunStub { id: UUID; job_id?: string; workflow?: unknown; cadence?: unknown; } interface CreateLeadResponse { success: true; lead: Lead; /** True when an existing lead was matched/merged (vs. a brand-new insert). */ existed?: boolean; workflow_run?: WorkflowRunStub; force_first_message?: { sent: boolean; channel: "whatsapp" | "email"; error?: string; }; warning?: string; } interface BulkResultEntry { success: boolean; index: number; lead?: Lead; workflow_run?: WorkflowRunStub; tags?: UUID[]; error?: string; warning?: string; } interface CreateLeadBulkResponse { success: boolean; results: BulkResultEntry[]; summary: { total: number; created: number; failed: number; }; } interface GetLeadResponse { success: true; lead: Lead & { active_workflow_run: unknown | null; }; variables: Record; engagement: { whatsapp: { last_sent_at: ISODateString | null; last_delivered_at: ISODateString | null; last_read_at: ISODateString | null; last_reply_at: ISODateString | null; window_open: boolean; }; email: { last_sent_at: ISODateString | null; last_delivered_at: ISODateString | null; last_opened_at: ISODateString | null; }; call: { last_call_at: ISODateString | null; last_successful_call_at: ISODateString | null; total_calls: number; }; }; } interface LeadHistoryItem { id: string; type: string; channel: string; timestamp: ISODateString; direction?: "inbound" | "outbound" | null; content?: string; [key: string]: unknown; } interface GetLeadHistoryResponse { success: true; lead: { id: UUID; first_name: string; }; history: LeadHistoryItem[]; pagination: { limit: number; offset: number; has_more: boolean; }; } interface GetLeadHistoryParams { channel?: ("whatsapp" | "email" | "call") | Array<"whatsapp" | "email" | "call">; limit?: number; offset?: number; } interface SyncTagsInput { /** Either lead_id or email is required. */ lead_id?: UUID; email?: string; /** Source-of-truth set; [] clears all tags. */ tags: string[]; } interface SyncTagsResponse { success: true; lead_id: UUID; tags: unknown; added: unknown[]; removed: unknown[]; } interface Workflow { id: UUID; name: string; description: string | null; goal_type: string | null; cadence_enabled: boolean; created_at: ISODateString; } interface Campaign { id: UUID; name: string; description: string | null; created_at: ISODateString; } interface WhatsAppTemplate { id: UUID; name: string; language: string; category: string; status: string; components: unknown; variables: unknown; } interface ListTemplatesParams { status?: string; category?: string; } interface SendMessageInput { lead_id: UUID; workflow_id: UUID; channel: "whatsapp" | "email" | "call"; /** WhatsApp / email text body. */ content?: string; /** Email HTML body. */ html?: string; /** Email subject. */ subject?: string; /** WhatsApp template id (alternative to free-form content). */ template_id?: UUID; /** WhatsApp template component overrides. */ components?: unknown; /** Retell first-utterance text when channel === "call". */ begin_message?: string; } interface CreateMeetingInput { lead_email: string; title: string; starts_at: ISODateString; ends_at?: ISODateString; duration_minutes?: number; timezone?: string; meeting_url?: string; location_type?: string; location_details?: string; description?: string; host_email?: string; meeting_type_id?: UUID; attendee_name?: string; attendee_email?: string; attendee_phone?: string; additional_attendees?: Array<{ name?: string; email: string; }>; external_calendar_provider?: string; external_event_id?: string; status?: string; } interface MeetingNotesInput { lead_email?: string; notes?: string; manual_ai_summary?: string; provider?: string; external_meeting_id?: string; calendar_event_id?: UUID; external_calendar_provider?: string; external_calendar_event_id?: string; meeting_url?: string; meeting_date?: ISODateString; meeting_title?: string; duration_seconds?: number; organizer_email?: string; transcript_text?: string; summary?: string; action_items?: string[]; questions?: string[]; keywords?: string[]; sentences?: unknown[]; status?: "completed" | "pending" | "failed"; } interface AutomationInput { workflow_id: UUID; } interface ResumeAutomationInput extends AutomationInput { resume_cadence?: boolean; } interface IsPausedResponse { success: true; paused: boolean; } /** * Low-level Nexor API client. * * Most users should call `init()` / `createClient()` instead of instantiating * this directly, but it's exported for advanced cases (DI, mocking). */ declare class NexorClient { private cfg; constructor(opts: ClientOptions); /** Create a single lead, optionally assigning it to a workflow. */ createLead(input: CreateLeadInput, options?: RequestOptions): Promise; /** Create many leads in one request (max 1000). */ createLeadsBulk(inputs: CreateLeadInput[], options?: RequestOptions): Promise; /** Update an existing lead. metadata is shallow-merged server-side. */ updateLead(leadId: UUID, updates: Partial, options?: RequestOptions): Promise<{ success: true; lead: Lead; }>; /** Fetch a lead with its captured variables and per-channel engagement. */ getLead(leadId: UUID, options?: RequestOptions): Promise; /** Full chronological history: messages, transcripts, activity. */ getLeadHistory(leadId: UUID, params?: GetLeadHistoryParams, options?: RequestOptions): Promise; /** Has automation been paused (human takeover) for this lead? */ isLeadPaused(leadId: UUID, params?: { workflow_id?: UUID; }, options?: RequestOptions): Promise; /** Pause automation (engage human takeover) on a specific workflow run. */ stopAutomation(leadId: UUID, input: AutomationInput, options?: RequestOptions): Promise; /** Resume automation; pass resume_cadence: true to restart scheduled touches. */ resumeAutomation(leadId: UUID, input: ResumeAutomationInput, options?: RequestOptions): Promise; /** * Replace the tag set on a lead (identified by lead_id OR email). * Tags missing from the input are unassigned; new tags are find-or-created. */ syncLeadTags(input: SyncTagsInput, options?: RequestOptions): Promise; /** All meetings on file for a lead (optionally filter by status). */ listLeadMeetings(leadId: UUID, params?: { status?: string | string[]; }, options?: RequestOptions): Promise; /** Active workflows in your account. */ listWorkflows(options?: RequestOptions): Promise<{ success: true; workflows: Workflow[]; }>; /** Campaigns in your account. */ listCampaigns(options?: RequestOptions): Promise<{ success: true; campaigns: Campaign[]; }>; /** Approved WhatsApp templates. */ listTemplates(params?: ListTemplatesParams, options?: RequestOptions): Promise<{ success: true; templates: WhatsAppTemplate[]; }>; /** Send a one-off message (WhatsApp/email) or trigger a call. */ sendMessage(input: SendMessageInput, options?: RequestOptions): Promise; /** Log a booked meeting against an existing lead (matched by email). */ createMeeting(input: CreateMeetingInput, options?: RequestOptions): Promise; /** Push a note-taker transcript / summary / action items to a meeting. */ createMeetingNotes(input: MeetingNotesInput, options?: RequestOptions): Promise; /** * Fetch the dashboard-authored web-chat config for a workflow. Backed by * GET /api/public/chat/config. The widget calls this once on init to learn * whether web chat is enabled and to get the static opening message * (`initial_message`, a RAW template that may contain {{variables}} — the * widget substitutes them client-side before painting the greeting). * * Most users will not call this directly — `initChat()` handles it. */ getChatConfig(workflowId: UUID, options?: RequestOptions): Promise<{ success: true; enabled: boolean; initial_message: string | null; }>; /** * Send a turn in a browser-embedded chat session. Backed by * POST /api/public/chat on the Nexor API. * * Most users will not call this directly — `initChat()` handles it. */ chat(input: { workflow_id: UUID; session_id: string; message: string; system_prompt?: string; client_prompt?: string; /** The opening message the widget actually displayed (keeps the agent's * in sync with a per-page override). */ opening_shown?: string; /** Start over as a brand-new lead: the API skips email/phone/session * matching and mints a fresh lead with no prior conversation context. */ new_lead?: boolean; /** Keep the same lead but start a fresh conversation episode: the API * deactivates the prior run + thread and clears this workflow's collected * variables, so the agent sees no prior context. */ reset_episode?: boolean; lead?: { first_name?: string; last_name?: string; email?: string; phone?: string; metadata?: Record; }; metadata?: Record; }, options?: RequestOptions): Promise<{ success: true; reply: string; session_id: string; lead_id?: UUID; workflow_run_id?: UUID; /** True when the API recognised an existing lead (returning visitor). */ matched_existing?: boolean; }>; /** * Create (or upsert) a lead from the web chat widget and force its FIRST * contact to go out on a chosen channel — used when a visitor clicks * "call me" / "email me" instead of opening the chat. Backed by * POST /api/public/leads with `force_first_channel`. * * Unlike a literal `force_first_message`, `force_first_channel` pins the * channel and lets the workflow's own agent compose the first outreach. The * `metadata` you pass (page URL, requested channel, a note that the visitor * asked to be contacted from the website) is stored on the lead so the agent * has that context. * * Most users will not call this directly — `initChat()`'s contact-request * form handles it. */ requestContact(input: { workflow_id: UUID; first_name?: string; last_name?: string; email?: string; phone?: string; /** Force the first contact onto this channel (agent composes the message). */ force_first_channel: "call" | "email" | "whatsapp"; /** Context stored on the lead (page URL, requested channel, free-form note). */ metadata?: Record; /** Lead source label. Defaults to "web-chat" server-side handling. */ source?: string; }, options?: RequestOptions): Promise<{ success: true; lead: { id: UUID; first_name?: string; email?: string; phone?: string; }; existed?: boolean; workflow_run?: { id: UUID; }; force_first_channel?: { channel: string; applied: boolean; }; warning?: string; }>; } /** * Chat widget configuration types + defaults. * * Kept separate from the widget's runtime code so other modules (and the * top-level `nexor.initChat()` re-export) can pull types without dragging in * the DOM construction code. */ interface ChatLeadCapture { /** Which fields to ask for before chatting. Defaults to ["first_name", "email"]. */ fields?: Array<"first_name" | "last_name" | "email" | "phone">; /** "before" gates chat behind the form; "skip" never asks. Default "before". */ mode?: "before" | "skip"; /** Create the lead in Nexor the INSTANT the form is submitted (via POST * /leads), instead of waiting for the visitor's first chat message. Captures * visitors who fill the form but never send a message. Default false. */ createLeadOnSubmit?: boolean; /** Heading rendered above the form (its own view). */ label?: string; /** Submit button label. */ submitLabel?: string; /** Per-field placeholder overrides (for localization). */ placeholders?: Partial>; /** Default country for the phone field — a dial code ("+56") or ISO ("CL"). * Sets the initial flag, prefix and max length. */ phoneCountryCode?: string; /** Restrict the phone country selector to these ISO codes (e.g. ["CL","AR"]), * in this order. Omit to offer the full built-in list. */ phoneCountries?: string[]; /** Validation error copy (for localization). */ errorMessages?: { required?: string; email?: string; phone?: string; }; } /** The actions that can appear in the hover/speed-dial fan above the launcher. */ type ChatChannelKey = "whatsapp" | "call" | "email" | "instagram" | "chat"; /** Channels that can open the in-widget contact-request form. */ type ContactRequestChannel = "call" | "email" | "whatsapp"; /** * Turns the `call` / `email` (optionally `whatsapp`) fan buttons into an * in-widget contact-request FORM instead of a tel:/mailto: link. The visitor * fills name + email + phone, and on submit the widget creates a lead in Nexor * and forces the FIRST contact to go out on that channel (the workflow's agent * composes the message), with context that they requested it from the website. * * All copy is per-channel so it can be localized; sensible English defaults * apply when omitted. */ interface ChatRequestContact { /** Fan channels that open the form instead of navigating. Default ["call","email"]. */ channels?: ContactRequestChannel[]; /** Heading above the form, per channel. */ title?: Partial>; /** Explainer line (e.g. "we need this info to contact you"), per channel. */ subtitle?: Partial>; /** Submit button label, per channel. */ submitLabel?: Partial>; /** Confirmation shown after a successful request, per channel. */ successText?: Partial>; /** Label for the button that dismisses the success screen. Default "Done". */ doneLabel?: string; /** Error shown when the request fails. */ errorText?: string; } /** * Optional "speed-dial" channels shown when the visitor hovers (desktop) or taps * (touch) the launcher bubble. Instead of opening the chat immediately, the * launcher fans out a column of round buttons — WhatsApp, call, email, Instagram, * and an "open chat" button — letting the visitor pick a channel. * * If only `chat` would be enabled (no other channel supplied), the fan is skipped * and the launcher keeps its default behaviour (click opens the chat). */ interface ChatChannels { /** WhatsApp number in international format (e.g. "+56912345678"). Opens wa.me. */ whatsapp?: string; /** Pre-filled WhatsApp message text. */ whatsappText?: string; /** Phone number for the call button (e.g. "+56912345678"). Opens tel:. */ call?: string; /** Email address. Opens mailto:. */ email?: string; /** Instagram handle ("nexor.ai") or full profile URL. */ instagram?: string; /** Include a button that opens the chat panel. Defaults to true. */ chat?: boolean; /** Top-to-bottom order of the fan buttons. Defaults to the order below. */ order?: ChatChannelKey[]; /** Accessible labels / hover tooltips per channel. */ labels?: Partial>; /** Turn `call`/`email` (and optionally `whatsapp`) into an in-widget * contact-request form that creates a lead and forces first contact on that * channel, instead of opening a tel:/mailto: link. Omit to keep magic links. */ requestContact?: ChatRequestContact; } interface ChatInitOptions { /** Workflow that handles this chat conversation. Required. */ workflowId: UUID; title?: string; subtitle?: string; /** * Force the opening message for this widget instance. Highest priority — * WINS over the dashboard `initial_message` and over `greeting`. Use it for * per-page openings (e.g. an ecommerce-specific greeting on /ecommerce). * Supports the same {{first_name}} / {{last_name}} / {{full_name}} / {{email}} * / {{phone}} tokens, substituted from the captured/known lead. */ openingMessage?: string; /** Fallback opening, used only when neither `openingMessage` nor the * dashboard `initial_message` is set. */ greeting?: string; /** Shown when a turn fails or the server returns no reply. */ errorText?: string; /** Shown when web chat is disabled for the workflow (channel turned off in * the dashboard, or the API reports it unavailable). */ disabledText?: string; /** BCP-47 locale for timestamps (e.g. "es", "en-US"). Defaults to the browser. */ locale?: string; /** Hex colour for bubble + accents. Defaults to #111827. */ accentColor?: string; /** Text colour rendered on top of the accent. Defaults to white. */ accentTextColor?: string; /** Show the "Powered by Nexor" footer. Defaults to true. */ showBranding?: boolean; /** Composer placeholder once chat is active. Default "Type a message…". */ inputPlaceholder?: string; /** Show a header "start over" control that wipes the conversation and starts a * BRAND-NEW lead (the backend won't re-match the old one). Default true. */ showRestart?: boolean; /** aria-label / tooltip for the header restart control. Default "Start over". */ restartLabel?: string; position?: "bottom-right" | "bottom-left"; /** Auto-open the panel on first load. Defaults to false. */ openOnLoad?: boolean; /** * Turn the launcher into a hover/tap "speed-dial" that fans out channel * buttons (WhatsApp, call, email, Instagram, chat) instead of opening the * chat right away. Omit for the classic click-to-open launcher. */ channels?: ChatChannels; /** Coalesce rapid-fire messages: wait this many ms of quiet, then send the * buffered messages as one combined turn. Defaults to 700. 0 = per message. */ debounceMs?: number; /** Split a bot reply into separate bubbles on blank lines (paragraph breaks), * WhatsApp-style. Defaults to true. Set false to keep one bubble per reply. */ splitReplies?: boolean; /** Fixed typing pause (ms) shown between split bubbles. Omit for a natural * 3000–5000ms pause scaled by the length of the next message (longer text * reads as more time spent typing). */ splitDelayMs?: number; /** Per-turn request timeout (ms). Agent turns can legitimately take 10-20s, * so this is generous (default 60000). Chat turns never retry — a timeout * surfaces the error once rather than re-sending the message. */ requestTimeoutMs?: number; /** Append the widget to a custom element. Defaults to document.body. */ container?: HTMLElement; systemPrompt?: string; clientPrompt?: string; /** Pre-known lead info (e.g. a logged-in user). Skips capture if complete. */ lead?: { first_name?: string; last_name?: string; email?: string; phone?: string; metadata?: Record; }; capture?: ChatLeadCapture; /** Arbitrary metadata passed with each turn (page URL, UTM, …). Pass a * FUNCTION to have it resolved fresh on every turn — useful for SPA values * that change without re-mounting the widget (e.g. the current route). */ metadata?: Record | (() => Record | undefined); /** Banner text when a prior conversation is restored but we have no name. * Default "Continuing your conversation". */ resumeText?: string; /** Banner text when we know the visitor. `{name}` is replaced with their * first name (or email / phone as a fallback). Default "Continuing as {name}". */ resumeWithNameText?: string; /** Label for the reset action in the resume banner. Default "Start over". */ startOverText?: string; onOpen?: () => void; onClose?: () => void; onMessage?: (msg: { role: "user" | "bot"; text: string; }) => void; onLeadCaptured?: (leadId: UUID) => void; onError?: (err: Error) => void; } interface ChatHandle { open(): void; close(): void; toggle(): void; /** Programmatically send a user message. */ send(text: string): Promise; /** Tear down the widget and remove all DOM. */ destroy(): void; /** Read the current session id (auto-generated on first open). */ getSessionId(): string; } export { type AutomationInput as A, type BulkResultEntry as B, type Campaign as C, type ForceFirstMessage as F, type GetLeadHistoryParams as G, type ISODateString as I, type Lead as L, type MeetingNotesInput as M, NexorClient as N, type RequestOptions as R, type SendMessageInput as S, type UUID as U, type WhatsAppTemplate as W, type ChatChannelKey as a, type ChatChannels as b, type ChatHandle as c, type ChatInitOptions as d, type ChatLeadCapture as e, type ClientOptions as f, type CreateLeadBulkResponse as g, type CreateLeadInput as h, type CreateLeadResponse as i, type CreateMeetingInput as j, type GetLeadHistoryResponse as k, type GetLeadResponse as l, type IsPausedResponse as m, type LeadHistoryItem as n, type ListTemplatesParams as o, type ResumeAutomationInput as p, type SyncTagsInput as q, type SyncTagsResponse as r, type Workflow as s, type WorkflowRunStub as t };